Table of Contents

  • 1  Шаг Загружаем данные и изучаем общую информацию
    • 1.1  Загружаем библиотеки
    • 1.2  Загружаем данные mobile_sources и mobile_dataset
    • 1.3  Выводим общую информацию по таблицам mobile_sources и mobile_dataset
  • 2  Шаг Предобработка и исследовательский анализ данных
    • 2.1  Переименование столбцов
    • 2.2  Изменение типа данных
    • 2.3  Добавление столбца
    • 2.4  Объединение данных в столбце
    • 2.5  Исследовательский анализ данных
      • 2.5.1  Создаем таблицу unnecessary_things
      • 2.5.2  Исследуем данные по пользователям
      • 2.5.3  Исследуем поведение пользователя по источникам
      • 2.5.4  Исследуем поведение пользователя по действиям
      • 2.5.5  Исследуем поведение пользователей по времени
        • 2.5.5.1  Исследуем первые действия по пользователям
        • 2.5.5.2  Исследуем последние действия по пользователям
      • 2.5.6  Изучаем воронку событий
      • 2.5.7  Расчёт Retention Rate
      • 2.5.8  Расчёт Churn Rate
      • 2.5.9  Оценка пользовательской активности. Расчёт DAU, MAU, WAU и sticky factor
        • 2.5.9.1  Посчитаем DAU , WAU , MAU - «метрики тщеславия»
        • 2.5.9.2  Посчитаем sticky factor или «липкий фактор» - «метрики тщеславия»
      • 2.5.10  Продолжительности сеанса на каждого пользователя
  • 3  Шаг Анализ событий
    • 3.1  Создаем сессии по пользователям
      • 3.1.1  Строим диаграмму Санкея
      • 3.1.2  Строим воронки по выбранным сценариям
      • 3.1.3  Рассчет среднего времени от первого действия до Целевого
      • 3.1.4  Проанализируй связь целевого события - просмотр контактов - и другие действия
      • 3.1.5  События ADVERT_OPEN -> CONTACTS_SHOW
        • 3.1.5.1  Считаем время проведенное между событиями ADVERT_OPEN -> CONTACTS_SHOW
        • 3.1.5.2  Считаем конверсию по событиям ADVERT_OPEN -> CONTACTS_SHOW
      • 3.1.6  События TIPS_CLICK-> CONTACTS_SHOW
        • 3.1.6.1  Считаем время проведенное между событиями TIPS_CLICK-> CONTACTS_SHOW
        • 3.1.6.2  Считаем конверсию по событиям TIPS_CLICK-> CONTACTS_SHOW
  • 4  Проверка статистических гипотез
    • 4.1  Различается ли конверсия в просмотры контактов tips_show+tips_click и tips_show
      • 4.1.1  Формулируем гипотезу:
      • 4.1.2  Действия tips_show + tips_click
      • 4.1.3  Действия tips_show
    • 4.2  Различается ли конверсия просмотры контактов по источникам yandex и google
      • 4.2.1  Формулируем гипотезу:
  • 5  Шаг Выводы и рекомендации
    • 5.1  ВЫВОДЫ
    • 5.2  Рекомендации

Мобильное приложение "Ненужные вещи"¶

Цель исследования: - анализ поведения пользователей в мобильном приложении "Ненужные вещи"

Задачи:

1.Проанализировать связь целевого события - просмотр котатктов - и других действий пользователей;

2.Оценить, какие действия чаще совершают те пользователи, которые просматривают контакты:

  • Провести исследовательский анализ данных;
  • Проанализировать влияние событий на совершение событий на совершение целевого события;
  • Проверить статистические гипотезы:
    • Одни пользователи совершают действия tips_show и tips_click , другие — только tips_show . Проверьте гипотезу: конверсия в просмотры контактов различается у этих двух групп.
    • Сформулируйте собственную статистическую гипотезу. Дополните её нулевой и альтернативной гипотезами. Проверьте гипотезу с помощью статистического теста.

Описание данных:

Датасет содержит данные о событиях, совершенных в мобильном приложении "Ненужные вещи". В нем пользователи продают свои ненужные вещи, размещая их на доске объявлений.

В датасете содержатся данные пользователей, впервые совершивших действия в приложении после 7 октября 2019 года.

Шаг Загружаем данные и изучаем общую информацию¶

In [1]:
!pip install plotly chart_studio
Requirement already satisfied: plotly in c:\users\tima\anaconda3\lib\site-packages (5.9.0)
Requirement already satisfied: chart_studio in c:\users\tima\anaconda3\lib\site-packages (1.1.0)
Requirement already satisfied: tenacity>=6.2.0 in c:\users\tima\anaconda3\lib\site-packages (from plotly) (8.0.1)
Requirement already satisfied: six in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (1.16.0)
Requirement already satisfied: requests in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (2.28.1)
Requirement already satisfied: retrying>=1.3.3 in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (1.3.4)
Requirement already satisfied: idna<4,>=2.5 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (3.3)
Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (2022.9.14)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (1.26.11)

Загружаем библиотеки¶

In [2]:
# импортируем библиотеки
import pandas as pd
import numpy as np
from scipy import stats as st
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
sns.set(rc={'figure.figsize':(16, 9)})
import datetime
import plotly.express as px
from plotly import graph_objects as go
import requests
from tqdm.auto import tqdm
import chart_studio.plotly as py
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)

Загружаем данные mobile_sources и mobile_dataset¶

In [3]:
# загружаем данные
mobile_sources = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_sources.csv') 
mobile_dataset = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_dataset.csv')

Выводим общую информацию по таблицам mobile_sources и mobile_dataset¶

In [4]:
# выводим общую информацию по таблице  mobile_sources
display(mobile_sources.head(10))
display(mobile_sources.describe()) 
display(mobile_sources.shape)
mobile_sources.info()
print('Количество дубликатов:', mobile_sources.duplicated().sum())
userId source
0 020292ab-89bc-4156-9acf-68bc2783f894 other
1 cf7eda61-9349-469f-ac27-e5b6f5ec475c yandex
2 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 yandex
3 d9b06b47-0f36-419b-bbb0-3533e582a6cb other
4 f32e1e2a-3027-4693-b793-b7b3ff274439 google
5 17f6b2db-2964-4d11-89d8-7e38d2cb4750 yandex
6 62aa104f-592d-4ccb-8226-2ba0e719ded5 yandex
7 57321726-5d66-4d51-84f4-c797c35dcf2b google
8 c2cf55c0-95f7-4269-896c-931d14deaab5 google
9 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 yandex
userId source
count 4293 4293
unique 4293 3
top 020292ab-89bc-4156-9acf-68bc2783f894 yandex
freq 1 1934
(4293, 2)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4293 entries, 0 to 4292
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   userId  4293 non-null   object
 1   source  4293 non-null   object
dtypes: object(2)
memory usage: 67.2+ KB
Количество дубликатов: 0
In [5]:
# выводим общую информацию по таблице  mobile_dataset
display(mobile_dataset.head(10))
display(mobile_dataset.describe()) 
display(mobile_dataset.shape)
mobile_dataset.info()
print('Количество дубликатов:', mobile_dataset.duplicated().sum())
event.time event.name user.id
0 2019-10-07 00:00:00.431357 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
1 2019-10-07 00:00:01.236320 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
2 2019-10-07 00:00:02.245341 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
3 2019-10-07 00:00:07.039334 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
4 2019-10-07 00:00:56.319813 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
5 2019-10-07 00:01:19.993624 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
6 2019-10-07 00:01:27.770232 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
7 2019-10-07 00:01:34.804591 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
8 2019-10-07 00:01:49.732803 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
9 2019-10-07 00:01:54.958298 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
event.time event.name user.id
count 74197 74197 74197
unique 74197 16 4293
top 2019-10-07 00:00:00.431357 tips_show cb36854f-570a-41f4-baa8-36680b396370
freq 1 40055 478
(74197, 3)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 3 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   event.time  74197 non-null  object
 1   event.name  74197 non-null  object
 2   user.id     74197 non-null  object
dtypes: object(3)
memory usage: 1.7+ MB
Количество дубликатов: 0

Таблица mobile_sources состоит из 2 столбцов и 4293 строк. Название столбцов:userId и source имеют тип данных object. Таблица mobile_dataset состоит из 3 столбцов и 74197 строк.Название столбцов:event.time, event.name и user.id имеют тип данных object. Количество уникальных значений в столбце user-Id совпадает в таблицах: mobile_sources и mobile_dataset, и составляет 4293. 16 видов действий учавствует в данных. 3 вида источника, с которых пользователь установил приложение. Дубликатов в таблицах нет.Необходимо изменить название столбцов и изменить тип данных в столбце event.time. В данных пропусков нет.

Шаг Предобработка и исследовательский анализ данных¶

Переименование столбцов¶

In [6]:
# меняем название столбцов в таблицах для придания результатам более читабельный вид и для объединения таблиц
mobile_sources.rename(columns = {'userId':'user_id'}, inplace = True)
mobile_dataset.rename(columns = {'user.id':'user_id', 'event.time':'time', 'event.name':'event'}, inplace = True)
display(mobile_sources.head(10))
mobile_dataset.head(10)
user_id source
0 020292ab-89bc-4156-9acf-68bc2783f894 other
1 cf7eda61-9349-469f-ac27-e5b6f5ec475c yandex
2 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 yandex
3 d9b06b47-0f36-419b-bbb0-3533e582a6cb other
4 f32e1e2a-3027-4693-b793-b7b3ff274439 google
5 17f6b2db-2964-4d11-89d8-7e38d2cb4750 yandex
6 62aa104f-592d-4ccb-8226-2ba0e719ded5 yandex
7 57321726-5d66-4d51-84f4-c797c35dcf2b google
8 c2cf55c0-95f7-4269-896c-931d14deaab5 google
9 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 yandex
Out[6]:
time event user_id
0 2019-10-07 00:00:00.431357 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
1 2019-10-07 00:00:01.236320 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
2 2019-10-07 00:00:02.245341 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
3 2019-10-07 00:00:07.039334 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
4 2019-10-07 00:00:56.319813 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
5 2019-10-07 00:01:19.993624 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
6 2019-10-07 00:01:27.770232 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
7 2019-10-07 00:01:34.804591 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
8 2019-10-07 00:01:49.732803 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
9 2019-10-07 00:01:54.958298 advert_open 020292ab-89bc-4156-9acf-68bc2783f894

Изменение типа данных¶

In [7]:
# переводим столбец time в формат 
mobile_dataset['time'] = pd.to_datetime(mobile_dataset['time'])
mobile_dataset.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype         
---  ------   --------------  -----         
 0   time     74197 non-null  datetime64[ns]
 1   event    74197 non-null  object        
 2   user_id  74197 non-null  object        
dtypes: datetime64[ns](1), object(2)
memory usage: 1.7+ MB

Добавление столбца¶

In [8]:
#создаем столбец date
mobile_dataset['data'] = pd.to_datetime(mobile_dataset['time'].dt.date) 
mobile_dataset.info() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype         
---  ------   --------------  -----         
 0   time     74197 non-null  datetime64[ns]
 1   event    74197 non-null  object        
 2   user_id  74197 non-null  object        
 3   data     74197 non-null  datetime64[ns]
dtypes: datetime64[ns](2), object(2)
memory usage: 2.3+ MB

Объединение данных в столбце¶

In [9]:
# заменяем неявные дубликаты в столбце event название show_contacts на contacts_show
mobile_dataset['event'] = mobile_dataset['event'].replace(['show_contacts'], 'contacts_show')
In [10]:
# объеденим в столбце event search_1-search_7 в search
mobile_dataset['event'] = mobile_dataset['event'].str.replace('search_\d', 'search', regex=True)
In [11]:
mobile_dataset['event'].value_counts()
Out[11]:
tips_show        40055
photos_show      10012
search            6784
advert_open       6164
contacts_show     4529
map               3881
favorites_add     1417
tips_click         814
contacts_call      541
Name: event, dtype: int64

Изменили название столбцов в таблицах: mobile_dataset и mobile_sources. В таблице mobile_sources название столбца 'userId' и в таблице mobile_sources название столбца 'user.id' заменили на 'user_id'. Также в таблице mobile_dataset заменили название столбцов 'event.time' на 'time', 'event.name' на 'event'. Изменили тип данных в столбце time с object на datetime64. Добавили столбец data с типом данных datetime64.Объеденили в столбце event названия действий: show_contacts в contacts_show и search_1', 'search_2', 'search_3', 'search_4', 'search_5', 'search_6', 'search_7' в 'search'. Добавили столбцы date и week.Теперь можно объединять таблицы для анализа.

Исследовательский анализ данных¶

Создаем таблицу unnecessary_things¶

In [12]:
# соединяем с помощью merge данные в одну таблицу
unnecessary_things = mobile_sources.merge(mobile_dataset, on='user_id', how='left')
unnecessary_things
Out[12]:
user_id source time event data
0 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:00.431357 advert_open 2019-10-07
1 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:01.236320 tips_show 2019-10-07
2 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:07.039334 tips_show 2019-10-07
3 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:27.770232 advert_open 2019-10-07
4 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:34.804591 tips_show 2019-10-07
... ... ... ... ... ...
74192 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:47.068179 map 2019-11-03
74193 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:58.914787 advert_open 2019-11-03
74194 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:01.232230 tips_show 2019-11-03
74195 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:47.475102 advert_open 2019-11-03
74196 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:50.087645 tips_show 2019-11-03

74197 rows × 5 columns

Исследуем данные по пользователям¶

In [13]:
# распределение событий по пользователям
print(f'Всего в логе {len(unnecessary_things)} событий.')
print(f'Всего пользователей в логе {len(unnecessary_things.user_id.unique())}.')
print(f'В среднем на пользователя приходится {int(len(unnecessary_things) / len(unnecessary_things.user_id.unique()))} событий.')
unnecessary_things.groupby('user_id')[['event']].count().describe(percentiles=[0.05, 1/4, 1/2, 3/4, 0.95, 0.99])
Всего в логе 74197 событий.
Всего пользователей в логе 4293.
В среднем на пользователя приходится 17 событий.
Out[13]:
event
count 4293.000000
mean 17.283252
std 29.130677
min 1.000000
5% 3.000000
25% 5.000000
50% 9.000000
75% 17.000000
95% 59.000000
99% 132.000000
max 478.000000
In [14]:
# строим гистограмму распределение действий по пользователям
plt.figure(figsize=(15, 7))
sns.histplot(data=unnecessary_things.groupby('user_id')[['event']].count(), x='event', kde=True)
plt.title('Распределение действий по пользователям')
plt.xlabel('Количество действий')
plt.ylabel('Количество пользователей')
plt.xlim(0,50)
plt.show()

В среднем, на каждого пользователя приходится 17 действия. Медианное количество при этом составляет 9. Минимальное значение 1, максимальное количество действий 478.

Исследуем поведение пользователя по источникам¶

In [15]:
# распределение событий по источникам
print(f'Всего источников в логе {unnecessary_things.source.value_counts().count()}.')
unnecessary_things['source'].value_counts()
Всего источников в логе 3.
Out[15]:
yandex    34286
google    20445
other     19466
Name: source, dtype: int64
In [16]:
# распределение источников по дням
unnecessary_things.pivot_table(
    index='data',  # день 
    columns='source',  # источники переходов
    values='user_id',  # ID пользователей
    aggfunc='nunique'  # подсчёт уникальных значений
).plot(figsize=(15, 5), grid=False)
plt.title('Распределение источников по дням')
plt.xlabel('День')
plt.ylabel('Источник')
plt.show();

В течении 28 дней пользователи с разных источников заходили в приложениене. У всех источников были всплески и падения по дням. Больше всего событий проходило из источника yandex(34286). В 1,7 раза меньше google(20445) и other(19466). Заметны снижения активности пользователй в выходные дни.

Исследуем поведение пользователя по действиям¶

In [17]:
# распределение событий по действиям пользователя
print(f'Всего действий в логе {unnecessary_things.event.value_counts().count()}.')
unnecessary_things['event'].value_counts()
Всего действий в логе 9.
Out[17]:
tips_show        40055
photos_show      10012
search            6784
advert_open       6164
contacts_show     4529
map               3881
favorites_add     1417
tips_click         814
contacts_call      541
Name: event, dtype: int64
In [18]:
# распределение действий по дням
unnecessary_things.pivot_table(
    index='data',  # день 
    columns='event',  # действия переходов
    values='user_id',  # ID пользователей
    aggfunc='nunique'  # подсчёт уникальных значений
).plot(figsize=(15, 5), grid=False)
plt.title('Распределение действий по дням')
plt.xlabel('День')
plt.ylabel('Действия')
plt.show();
In [19]:
# строим гистограмму по действиям и дате
data_event = unnecessary_things.groupby(['data','event']).agg({'time':'count'}).reset_index()
data_event.columns = ['data', 'event', 'count_event']
data_event.sort_values(by='count_event', ascending=False)
data_event
Out[19]:
data event count_event
0 2019-10-07 advert_open 401
1 2019-10-07 contacts_call 7
2 2019-10-07 contacts_show 61
3 2019-10-07 favorites_add 40
4 2019-10-07 map 168
5 2019-10-07 photos_show 230
6 2019-10-07 search 127
7 2019-10-07 tips_click 27
8 2019-10-07 tips_show 1484
9 2019-10-08 advert_open 206
10 2019-10-08 contacts_call 23
11 2019-10-08 contacts_show 119
12 2019-10-08 favorites_add 97
13 2019-10-08 map 261
14 2019-10-08 photos_show 239
15 2019-10-08 search 212
16 2019-10-08 tips_click 26
17 2019-10-08 tips_show 1316
18 2019-10-09 advert_open 130
19 2019-10-09 contacts_call 10
20 2019-10-09 contacts_show 99
21 2019-10-09 favorites_add 43
22 2019-10-09 map 133
23 2019-10-09 photos_show 235
24 2019-10-09 search 178
25 2019-10-09 tips_click 17
26 2019-10-09 tips_show 1182
27 2019-10-10 advert_open 105
28 2019-10-10 contacts_call 19
29 2019-10-10 contacts_show 106
30 2019-10-10 favorites_add 30
31 2019-10-10 map 163
32 2019-10-10 photos_show 218
33 2019-10-10 search 185
34 2019-10-10 tips_click 27
35 2019-10-10 tips_show 1390
36 2019-10-11 advert_open 109
37 2019-10-11 contacts_call 6
38 2019-10-11 contacts_show 88
39 2019-10-11 favorites_add 16
40 2019-10-11 map 108
41 2019-10-11 photos_show 381
42 2019-10-11 search 202
43 2019-10-11 tips_click 17
44 2019-10-11 tips_show 1103
45 2019-10-12 advert_open 141
46 2019-10-12 contacts_call 10
47 2019-10-12 contacts_show 54
48 2019-10-12 favorites_add 37
49 2019-10-12 map 112
50 2019-10-12 photos_show 302
51 2019-10-12 search 173
52 2019-10-12 tips_click 17
53 2019-10-12 tips_show 997
54 2019-10-13 advert_open 286
55 2019-10-13 contacts_call 4
56 2019-10-13 contacts_show 107
57 2019-10-13 favorites_add 44
58 2019-10-13 map 152
59 2019-10-13 photos_show 373
60 2019-10-13 search 194
61 2019-10-13 tips_click 11
62 2019-10-13 tips_show 1464
63 2019-10-14 advert_open 166
64 2019-10-14 contacts_call 20
65 2019-10-14 contacts_show 212
66 2019-10-14 favorites_add 49
67 2019-10-14 map 179
68 2019-10-14 photos_show 324
69 2019-10-14 search 238
70 2019-10-14 tips_click 43
71 2019-10-14 tips_show 1803
72 2019-10-15 advert_open 297
73 2019-10-15 contacts_call 32
74 2019-10-15 contacts_show 212
75 2019-10-15 favorites_add 63
76 2019-10-15 map 148
77 2019-10-15 photos_show 344
78 2019-10-15 search 241
79 2019-10-15 tips_click 25
80 2019-10-15 tips_show 1360
81 2019-10-16 advert_open 254
82 2019-10-16 contacts_call 17
83 2019-10-16 contacts_show 187
84 2019-10-16 favorites_add 21
85 2019-10-16 map 148
86 2019-10-16 photos_show 279
87 2019-10-16 search 239
88 2019-10-16 tips_click 20
89 2019-10-16 tips_show 1565
90 2019-10-17 advert_open 164
91 2019-10-17 contacts_call 35
92 2019-10-17 contacts_show 265
93 2019-10-17 favorites_add 56
94 2019-10-17 map 160
95 2019-10-17 photos_show 347
96 2019-10-17 search 187
97 2019-10-17 tips_click 42
98 2019-10-17 tips_show 1362
99 2019-10-18 advert_open 186
100 2019-10-18 contacts_call 16
101 2019-10-18 contacts_show 166
102 2019-10-18 favorites_add 35
103 2019-10-18 map 127
104 2019-10-18 photos_show 333
105 2019-10-18 search 268
106 2019-10-18 tips_click 37
107 2019-10-18 tips_show 1576
108 2019-10-19 advert_open 191
109 2019-10-19 contacts_call 20
110 2019-10-19 contacts_show 90
111 2019-10-19 favorites_add 42
112 2019-10-19 map 122
113 2019-10-19 photos_show 454
114 2019-10-19 search 231
115 2019-10-19 tips_click 9
116 2019-10-19 tips_show 1172
117 2019-10-20 advert_open 132
118 2019-10-20 contacts_call 30
119 2019-10-20 contacts_show 183
120 2019-10-20 favorites_add 50
121 2019-10-20 map 131
122 2019-10-20 photos_show 405
123 2019-10-20 search 223
124 2019-10-20 tips_click 26
125 2019-10-20 tips_show 964
126 2019-10-21 advert_open 249
127 2019-10-21 contacts_call 15
128 2019-10-21 contacts_show 175
129 2019-10-21 favorites_add 35
130 2019-10-21 map 263
131 2019-10-21 photos_show 316
132 2019-10-21 search 268
133 2019-10-21 tips_click 29
134 2019-10-21 tips_show 1558
135 2019-10-22 advert_open 164
136 2019-10-22 contacts_call 19
137 2019-10-22 contacts_show 215
138 2019-10-22 favorites_add 67
139 2019-10-22 map 149
140 2019-10-22 photos_show 358
141 2019-10-22 search 283
142 2019-10-22 tips_click 25
143 2019-10-22 tips_show 1448
144 2019-10-23 advert_open 224
145 2019-10-23 contacts_call 22
146 2019-10-23 contacts_show 239
147 2019-10-23 favorites_add 55
148 2019-10-23 map 196
149 2019-10-23 photos_show 502
150 2019-10-23 search 278
151 2019-10-23 tips_click 37
152 2019-10-23 tips_show 1808
153 2019-10-24 advert_open 244
154 2019-10-24 contacts_call 35
155 2019-10-24 contacts_show 220
156 2019-10-24 favorites_add 68
157 2019-10-24 map 163
158 2019-10-24 photos_show 378
159 2019-10-24 search 290
160 2019-10-24 tips_click 28
161 2019-10-24 tips_show 1607
162 2019-10-25 advert_open 215
163 2019-10-25 contacts_call 19
164 2019-10-25 contacts_show 188
165 2019-10-25 favorites_add 51
166 2019-10-25 map 119
167 2019-10-25 photos_show 352
168 2019-10-25 search 253
169 2019-10-25 tips_click 25
170 2019-10-25 tips_show 1396
171 2019-10-26 advert_open 274
172 2019-10-26 contacts_call 19
173 2019-10-26 contacts_show 181
174 2019-10-26 favorites_add 38
175 2019-10-26 map 141
176 2019-10-26 photos_show 577
177 2019-10-26 search 317
178 2019-10-26 tips_click 43
179 2019-10-26 tips_show 1537
180 2019-10-27 advert_open 287
181 2019-10-27 contacts_call 16
182 2019-10-27 contacts_show 101
183 2019-10-27 favorites_add 70
184 2019-10-27 map 123
185 2019-10-27 photos_show 507
186 2019-10-27 search 268
187 2019-10-27 tips_click 53
188 2019-10-27 tips_show 1436
189 2019-10-28 advert_open 305
190 2019-10-28 contacts_call 31
191 2019-10-28 contacts_show 198
192 2019-10-28 favorites_add 18
193 2019-10-28 map 226
194 2019-10-28 photos_show 332
195 2019-10-28 search 315
196 2019-10-28 tips_click 48
197 2019-10-28 tips_show 1711
198 2019-10-29 advert_open 285
199 2019-10-29 contacts_call 12
200 2019-10-29 contacts_show 162
201 2019-10-29 favorites_add 76
202 2019-10-29 map 84
203 2019-10-29 photos_show 375
204 2019-10-29 search 370
205 2019-10-29 tips_click 37
206 2019-10-29 tips_show 1795
207 2019-10-30 advert_open 237
208 2019-10-30 contacts_call 25
209 2019-10-30 contacts_show 190
210 2019-10-30 favorites_add 120
211 2019-10-30 map 68
212 2019-10-30 photos_show 313
213 2019-10-30 search 292
214 2019-10-30 tips_click 37
215 2019-10-30 tips_show 1595
216 2019-10-31 advert_open 270
217 2019-10-31 contacts_call 29
218 2019-10-31 contacts_show 234
219 2019-10-31 favorites_add 37
220 2019-10-31 map 61
221 2019-10-31 photos_show 348
222 2019-10-31 search 247
223 2019-10-31 tips_click 23
224 2019-10-31 tips_show 1619
225 2019-11-01 advert_open 228
226 2019-11-01 contacts_call 12
227 2019-11-01 contacts_show 223
228 2019-11-01 favorites_add 40
229 2019-11-01 map 72
230 2019-11-01 photos_show 305
231 2019-11-01 search 216
232 2019-11-01 tips_click 43
233 2019-11-01 tips_show 1438
234 2019-11-02 advert_open 88
235 2019-11-02 contacts_call 18
236 2019-11-02 contacts_show 122
237 2019-11-02 favorites_add 44
238 2019-11-02 map 39
239 2019-11-02 photos_show 331
240 2019-11-02 search 210
241 2019-11-02 tips_click 14
242 2019-11-02 tips_show 987
243 2019-11-03 advert_open 326
244 2019-11-03 contacts_call 20
245 2019-11-03 contacts_show 132
246 2019-11-03 favorites_add 75
247 2019-11-03 map 65
248 2019-11-03 photos_show 554
249 2019-11-03 search 279
250 2019-11-03 tips_click 28
251 2019-11-03 tips_show 1382
In [20]:
# строим столбчатую диаграмму 
fig = px.bar(data_event.sort_values(by='count_event', ascending=False), # загружаем данные и заново их сортируем
             y='count_event', # указываем столбец с данными для оси X
             x='data', # указываем столбец с данными для оси Y
             text='count_event', # добавляем аргумент, который отобразит текст с информацией
                                # о количестве объявлений внутри столбца графика
             color = 'event'
            )
# оформляем график
fig.update_layout(title='Распределение действий по дням',
                   xaxis_title='Дата',
                   yaxis_title='Количество действий')
fig.show() # выводим график

В каждом действии есть всплески по времени.Особенно сильно заметны всплески у действий: tips_show, tips_click. Больше всего событий у tips_show(40055)54% от общего количества действий, в 4 меньше photos_show(10012)13% и остальные действия составляют 24130 - 33%. Заметный спад был 12-13,19-20,26-27 октября и 2,3 ноября. Все эти дни приходятся на выходные дни, т.е в основном пользователи пользуются приложением в буднии дни. tips_show показывается всем автоматически и не зависит от его действия.

Исследуем поведение пользователей по времени¶

In [21]:
# распределение событий по дате
print(unnecessary_things['time'].describe(percentiles=[0.05, 1/4, 1/2, 3/4, 0.95, 0.99]))
print()
print(f'Минимальная дата: {unnecessary_things.time.min()}.')
print()
print(f'Максимальная дата: {unnecessary_things.time.max()}.')
print()
print(f'Период исследования: {unnecessary_things.time.max() - unnecessary_things.time.min()}.')
count                          74197
unique                         74197
top       2019-10-07 00:00:00.431357
freq                               1
first     2019-10-07 00:00:00.431357
last      2019-11-03 23:58:12.532487
Name: time, dtype: object

Минимальная дата: 2019-10-07 00:00:00.431357.

Максимальная дата: 2019-11-03 23:58:12.532487.

Период исследования: 27 days 23:58:12.101130.

Минимальная дата: 2019-10-07 00:00:00.431357. Максимальная дата: 2019-11-03 23:58:12.532487. Период исследования составляет практически 28 дней без 2 минут.

Исследуем первые действия по пользователям¶
In [22]:
# отсортировываем данные по столбцам user_id time
start_end = unnecessary_things.sort_values(by=['user_id', 'time'])
start_end
Out[22]:
user_id source time event data
2171 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:39:45.989359 tips_show 2019-10-07
2172 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:40:31.052909 tips_show 2019-10-07
2173 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:41:05.722489 tips_show 2019-10-07
2174 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:43:20.735461 tips_show 2019-10-07
2175 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:45:30.917502 tips_show 2019-10-07
... ... ... ... ... ...
19048 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:23.959572 tips_show 2019-11-03
19049 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:57.899997 contacts_show 2019-11-03
19050 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:07:40.932077 tips_show 2019-11-03
19051 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:18.202734 tips_show 2019-11-03
19052 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:25.388712 tips_show 2019-11-03

74197 rows × 5 columns

In [23]:
# разделяем в таблице данные на начало и конец 
start_end =start_end.groupby('user_id').agg(
    {'time': ['min', 'max'], 'event': ['first', 'last']})
# преобразовываем MultiIndex в Index
start_end.columns = start_end.columns.map(''.join)
# меняем название столбцов
start_end = start_end.rename(columns={'timemin': 'time_min', 'timemax': 'time_max'\
           , 'eventfirst': 'event_first', 'eventlast': 'event_last'}).reset_index()
# считаем количество дней для каждого пользователя
start_end['amount_time'] = start_end['time_max'] - start_end['time_min']
start_end
Out[23]:
user_id time_min time_max event_first event_last amount_time
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13:39:45.989359 2019-10-22 11:30:52.807203 tips_show tips_show 14 days 21:51:06.817844
1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 21:34:33.849769 2019-11-03 17:12:09.708771 search contacts_show 14 days 19:37:35.859002
2 00463033-5717-4bf1-91b4-09183923b9df 2019-11-01 13:54:35.385028 2019-11-01 14:19:17.860053 photos_show photos_show 0 days 00:24:42.475025
3 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-18 22:14:05.555052 2019-10-31 21:42:15.606558 search search 12 days 23:28:10.051506
4 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 2019-10-29 02:17:12.342406 contacts_show photos_show 3 days 09:32:31.079042
... ... ... ... ... ... ...
4288 ffab8d8a-30bb-424a-a3ab-0b63ebbf7b07 2019-10-13 16:11:27.414960 2019-10-26 19:53:51.993545 map tips_show 13 days 03:42:24.578585
4289 ffc01466-fdb1-4460-ae94-e800f52eb136 2019-10-07 20:32:49.997044 2019-10-07 20:33:42.135500 photos_show contacts_show 0 days 00:00:52.138456
4290 ffcf50d9-293c-4254-8243-4890b030b238 2019-10-23 11:51:35.199237 2019-10-23 11:52:55.577369 tips_show map 0 days 00:01:20.378132
4291 ffe68f10-e48e-470e-be9b-eeb93128ff1a 2019-10-21 16:39:33.867145 2019-10-28 07:17:52.646652 search photos_show 6 days 14:38:18.779507
4292 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-10-12 00:57:21.241896 2019-11-03 16:08:25.388712 tips_show tips_show 22 days 15:11:04.146816

4293 rows × 6 columns

In [24]:
# первые действия по уникальным пользователям
start_end.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).sort_values(by='user_id', ascending=False)
Out[24]:
user_id
event_first
tips_show 1398
search 1160
map 807
photos_show 560
contacts_show 179
advert_open 127
favorites_add 49
tips_click 13
In [25]:
# строим распределение последних действий по пользователям
start_end.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение последних действий по пользователям')
plt.xlabel('Последних действия')
plt.ylabel('Количество пользователей')
plt.show();

В первых действиях нет contact_call. Количество действий снижается от tips_show до tips_clik. Особенно резкое снижение идет от tips_show(1368) до contacts_show(173).

Исследуем последние действия по пользователям¶
In [26]:
# последние действия по уникальным пользователям
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
Out[26]:
user_id
event_last
tips_show 2433
photos_show 752
search 338
contacts_show 275
advert_open 161
map 147
contacts_call 86
favorites_add 75
tips_click 26
In [27]:
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).sum()
Out[27]:
user_id    4293
dtype: int64
In [28]:
# строим распределение последних действий по пользователям
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение последних действий по пользователям')
plt.xlabel('Последних действия')
plt.ylabel('Количество пользователей')
plt.show();

Последнии действия все 9. Количество действий также снижается от tips_show(2403)57% от общего количества пользователей до tips_clik(26)0,6%, но не так резко как первых действиях. Порядок последних действий пользователей меняется по сравнению с первыми действиями.

Изучаем воронку событий¶

Построим воронку и узнаем какой процент пользователей через поисковую систему дошел до ЦС.

In [29]:
# выбираем уникальных пользователей в датасете по search(поиск по сайту)
search_event = unnecessary_things.query('event == "search"')
search_uniq = search_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
search_user = unnecessary_things.query('user_id == @ search_uniq')
# выбираем события , по которым будем строить воронку
search_tips_show_contacts_show = search_user.query('event == "search" or event == "tips_show"or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_search = search_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_search['conversion'] = round(funnel_search['user_id']/ funnel_search['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_search['funnel'] =  round(funnel_search['user_id'] / funnel_search['user_id'].shift(1)*100,2)
funnel_search
Out[29]:
event user_id conversion funnel
0 search 1666 58.58 NaN
1 tips_show 801 28.16 48.08
2 contacts_show 377 13.26 47.07
In [30]:
unnecessary_things
Out[30]:
user_id source time event data
0 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:00.431357 advert_open 2019-10-07
1 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:01.236320 tips_show 2019-10-07
2 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:07.039334 tips_show 2019-10-07
3 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:27.770232 advert_open 2019-10-07
4 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:34.804591 tips_show 2019-10-07
... ... ... ... ... ...
74192 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:47.068179 map 2019-11-03
74193 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:58.914787 advert_open 2019-11-03
74194 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:01.232230 tips_show 2019-11-03
74195 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:47.475102 advert_open 2019-11-03
74196 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:50.087645 tips_show 2019-11-03

74197 rows × 5 columns

In [31]:
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
    x = funnel_search['user_id'],
    y = funnel_search['event'],
    opacity = 0.6,
    textposition = "auto",
    textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через поиск по приложению', title_x = 0.5)
fig.show()

После поиска по сайту , увидили рекомендованные объявления 48% пользователей. И 47 % пользователей доходят до просмотра контактов. Конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.

Построим воронку и узнаем сколько пользователей дошло до просмотра контактов через рекомендованные объявления.

In [32]:
# выбираем уникальных пользователей в датасете по tips_click(кликнул рекомендованное объявление)
tips_click = unnecessary_things.query('event == "tips_click"')
tips_click_uniq = tips_click['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
tips_click_user = unnecessary_things.query('user_id == @ tips_click_uniq')
# выбираем события , по которым будем строить воронку
tips_click_tips_show_contacts_show = tips_click_user.query('event == "tips_click" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_tips_click = tips_click_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_tips_click['conversion'] = round(funnel_tips_click['user_id']/ funnel_tips_click['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_tips_click['funnel'] =  round(funnel_tips_click['user_id'] / funnel_tips_click['user_id'].shift(1)*100,2)
funnel_tips_click
Out[32]:
event user_id conversion funnel
0 tips_click 322 76.3 NaN
1 contacts_show 100 23.7 31.06
In [33]:
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
    x = funnel_tips_click['user_id'],
    y = funnel_tips_click['event'],
    opacity = 0.6,
    textposition = "auto",
    textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через рекомендованные объявления', title_x = 0.5)
fig.show()

Через рекомендованные объявления(322) до просмотра контактов доходят 100(31%) пользователей.

Расчёт Retention Rate¶

Retention Rate, или коэффициент удержания, показывает, как долго клиенты остаются с компанией. Это один из важнейших показателей «здоровья» бизнеса.

In [34]:
# создаем таблицу с первыми посещениями в приложении
first_date = unnecessary_things.groupby(['user_id'])['data'].min()
first_date.name = 'first_date'
retention_rate = unnecessary_things.join(first_date,on='user_id')
retention_rate
Out[34]:
user_id source time event data first_date
0 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:00.431357 advert_open 2019-10-07 2019-10-07
1 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:01.236320 tips_show 2019-10-07 2019-10-07
2 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:07.039334 tips_show 2019-10-07 2019-10-07
3 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:27.770232 advert_open 2019-10-07 2019-10-07
4 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:34.804591 tips_show 2019-10-07 2019-10-07
... ... ... ... ... ... ...
74192 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:47.068179 map 2019-11-03 2019-11-03
74193 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:58.914787 advert_open 2019-11-03 2019-11-03
74194 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:01.232230 tips_show 2019-11-03 2019-11-03
74195 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:47.475102 advert_open 2019-11-03 2019-11-03
74196 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:50.087645 tips_show 2019-11-03 2019-11-03

74197 rows × 6 columns

Получим день начала недели, за которую произошло событие. Он станет идентификатором недели.

In [35]:
# параметр unit метода pd.to_timedelta задаёт единицу измерения — в нашем случае дня: unit='d'. Вычтем из даты порядковый номер дня:
retention_rate['activity_week'] = pd.to_datetime(retention_rate['data'],
                                                unit='d') - pd.to_timedelta(retention_rate['data'].dt.dayofweek, unit='d')
retention_rate['first_activity_week'] = pd.to_datetime(retention_rate['first_date'],
                                                      unit='d') - pd.to_timedelta(retention_rate['first_date'].dt.dayofweek, unit='d')
In [36]:
# рассчитаем lifetime пользователя в рамках когорты.
retention_rate['lifetime'] = retention_rate['activity_week'] - retention_rate['first_activity_week']
retention_rate.sort_values(by= 'lifetime', ascending=False)
retention_rate['lifetime'] = retention_rate['lifetime'] / np.timedelta64(1,'W')
retention_rate.sort_values(by= 'lifetime', ascending=False)
retention_rate['lifetime'] = retention_rate['lifetime'].astype(int)
retention_rate
Out[36]:
user_id source time event data first_date activity_week first_activity_week lifetime
0 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:00.431357 advert_open 2019-10-07 2019-10-07 2019-10-07 2019-10-07 0
1 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:01.236320 tips_show 2019-10-07 2019-10-07 2019-10-07 2019-10-07 0
2 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:00:07.039334 tips_show 2019-10-07 2019-10-07 2019-10-07 2019-10-07 0
3 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:27.770232 advert_open 2019-10-07 2019-10-07 2019-10-07 2019-10-07 0
4 020292ab-89bc-4156-9acf-68bc2783f894 other 2019-10-07 00:01:34.804591 tips_show 2019-10-07 2019-10-07 2019-10-07 2019-10-07 0
... ... ... ... ... ... ... ... ... ...
74192 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:47.068179 map 2019-11-03 2019-11-03 2019-10-28 2019-10-28 0
74193 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:46:58.914787 advert_open 2019-11-03 2019-11-03 2019-10-28 2019-10-28 0
74194 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:01.232230 tips_show 2019-11-03 2019-11-03 2019-10-28 2019-10-28 0
74195 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:47.475102 advert_open 2019-11-03 2019-11-03 2019-10-28 2019-10-28 0
74196 d157bffc-264d-4464-8220-1cc0c42f43a9 google 2019-11-03 23:47:50.087645 tips_show 2019-11-03 2019-11-03 2019-10-28 2019-10-28 0

74197 rows × 9 columns

Посчитаем для каждой когорты количество активных пользователей на определённую "неделю жизни".

In [37]:
# группируем данные по когорте и lifetime
cohorts = retention_rate.groupby(['first_activity_week','lifetime']).agg({'user_id':'nunique'}).reset_index()
cohorts
Out[37]:
first_activity_week lifetime user_id
0 2019-10-07 0 1130
1 2019-10-07 1 272
2 2019-10-07 2 170
3 2019-10-07 3 119
4 2019-10-14 0 1166
5 2019-10-14 1 282
6 2019-10-14 2 155
7 2019-10-21 0 1094
8 2019-10-21 1 239
9 2019-10-28 0 903

Чтобы найти Retention Rate, нужно сперва получить число пользователей, изначально бывших в когорте, и на него разделить число пользователей в каждую следующую неделю. Найдём исходное количество пользователей в когорте. Возьмём их число на нулевую неделю:

In [38]:
# считаем количество пользователей, изначально бывших в когорте
initial_users_count = cohorts[cohorts['lifetime'] == 0][['first_activity_week','user_id']]
initial_users_count
#initial_users_count['first_activity_week'] = initial_users_count['first_activity_week'].dt.data
Out[38]:
first_activity_week user_id
0 2019-10-07 1130
4 2019-10-14 1166
7 2019-10-21 1094
9 2019-10-28 903
In [39]:
# переименновываем столбцы
initial_users_count = initial_users_count.rename(columns={'user_id':'cohort_users'})
# соединяем таблицы
cohorts = cohorts.merge(initial_users_count,on='first_activity_week')
cohorts
Out[39]:
first_activity_week lifetime user_id cohort_users
0 2019-10-07 0 1130 1130
1 2019-10-07 1 272 1130
2 2019-10-07 2 170 1130
3 2019-10-07 3 119 1130
4 2019-10-14 0 1166 1166
5 2019-10-14 1 282 1166
6 2019-10-14 2 155 1166
7 2019-10-21 0 1094 1094
8 2019-10-21 1 239 1094
9 2019-10-28 0 903 903
In [40]:
# разделим количество активных пользователей в каждую из недель на исходное число пользователей в когорте
cohorts['retention'] = cohorts['user_id']/cohorts['cohort_users']
cohorts
Out[40]:
first_activity_week lifetime user_id cohort_users retention
0 2019-10-07 0 1130 1130 1.000000
1 2019-10-07 1 272 1130 0.240708
2 2019-10-07 2 170 1130 0.150442
3 2019-10-07 3 119 1130 0.105310
4 2019-10-14 0 1166 1166 1.000000
5 2019-10-14 1 282 1166 0.241852
6 2019-10-14 2 155 1166 0.132933
7 2019-10-21 0 1094 1094 1.000000
8 2019-10-21 1 239 1094 0.218464
9 2019-10-28 0 903 903 1.000000
In [41]:
# рассчитаем Retention Rate по lifetime
retention = cohorts.pivot_table(index='first_activity_week',columns='lifetime',values='retention',aggfunc='sum')
retention
Out[41]:
lifetime 0 1 2 3
first_activity_week
2019-10-07 1.0 0.240708 0.150442 0.10531
2019-10-14 1.0 0.241852 0.132933 NaN
2019-10-21 1.0 0.218464 NaN NaN
2019-10-28 1.0 NaN NaN NaN
In [42]:
sns.set(style='white')
plt.figure(figsize=(7, 5))
plt.title('Удержание пользователей')
sns.heatmap(retention, annot=True, fmt='.1%', linewidths=1, linecolor='gray')
plt.xlabel('Лайфтайм')
plt.ylabel('Неделя')
plt.show();

Retention Rate коэффициент удержания низкий 24%, значит 76% отваливаются. С каждой новой когортой падает коэффициент удержания.Посчитаем коэффиент отскока Churn Rate.

Расчёт Churn Rate¶

Churn Rate — ещё один показатель, который помогает компаниям оценить потенциальную выручку и «здоровье» бизнеса в целом. Он показывает, какой процент пользователей прекращает использовать сервис с течением времени.

In [43]:
# группируем данные по first_activity_week и lifetime
cohorts1 = retention_rate.groupby(['first_activity_week','lifetime']).agg({'user_id':'nunique'}).reset_index()
initial_users_count1 = cohorts1[cohorts1['lifetime'] == 0][['first_activity_week','user_id']]
initial_users_count1 = initial_users_count1.rename(columns={'user_id':'cohort_users'})
cohorts1 = cohorts1.merge(initial_users_count1,on='first_activity_week')
cohorts1['rebound'] = 1-cohorts1['user_id']/cohorts1['cohort_users']
cohorts1
Out[43]:
first_activity_week lifetime user_id cohort_users rebound
0 2019-10-07 0 1130 1130 0.000000
1 2019-10-07 1 272 1130 0.759292
2 2019-10-07 2 170 1130 0.849558
3 2019-10-07 3 119 1130 0.894690
4 2019-10-14 0 1166 1166 0.000000
5 2019-10-14 1 282 1166 0.758148
6 2019-10-14 2 155 1166 0.867067
7 2019-10-21 0 1094 1094 0.000000
8 2019-10-21 1 239 1094 0.781536
9 2019-10-28 0 903 903 0.000000
In [44]:
rebound = cohorts1.pivot_table(index='first_activity_week',columns='lifetime',values='rebound',aggfunc='sum')
rebound
Out[44]:
lifetime 0 1 2 3
first_activity_week
2019-10-07 0.0 0.759292 0.849558 0.89469
2019-10-14 0.0 0.758148 0.867067 NaN
2019-10-21 0.0 0.781536 NaN NaN
2019-10-28 0.0 NaN NaN NaN
In [45]:
sns.set(style='white')
plt.figure(figsize=(10, 4))
plt.title('Коэффициент «отскока»')
sns.heatmap(rebound, annot=True, fmt='.1%', linewidths=1, linecolor='gray')
plt.xlabel('Лайфтайм')
plt.ylabel('Неделя')
plt.show();

Churn Rate коэффициент отскока высокий 76%. С каждой новой когортой увеливается коэффициент отскока. Что-то не устраивает в приложении пользователей.

Оценка пользовательской активности. Расчёт DAU, MAU, WAU и sticky factor¶

Юнит-экономика и когортный анализ — мощные инструменты для оценки финансового благополучия бизнеса. На этот вопрос отвечает пользовательская активность — количество уникальных активных пользователей за определённое время. Её измеряют тремя показателями:

  • DAU — количество уникальных пользователей в день;
  • WAU — количество уникальных пользователей в неделю;
  • MAU — количество уникальных пользователей в месяц.
Посчитаем DAU , WAU , MAU - «метрики тщеславия»¶
In [46]:
# выделяем год, номер месяца и номер недели из времени начала сессии
retention_rate['min_year'] = retention_rate['first_date'].dt.year
retention_rate['min_month'] = retention_rate['first_date'].dt.month
retention_rate['min_week'] = retention_rate['first_date'].dt.isocalendar().week
retention_rate['min_date'] = retention_rate['first_date'].dt.date
In [47]:
# cгруппируем данные по дате и неделе, посчитаем количество уникальных пользователей по столбцу user_id и найдём среднее.

dau_total = (
    retention_rate.groupby('min_date').agg({'user_id': 'nunique'}).mean()
)

wau_total = (
    retention_rate.groupby(['min_year', 'min_week']).agg({'user_id': 'nunique'}).mean()
)
    
print(f'DAU — количество уникальных пользователей в день - {int(dau_total)}.')
print(f'WAU — количество уникальных пользователей в неделю - {int(wau_total)}.') 
DAU — количество уникальных пользователей в день - 153.
WAU — количество уникальных пользователей в неделю - 1073.

Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073.

Посчитаем sticky factor или «липкий фактор» - «метрики тщеславия»¶

Ещё одна популярная «тщеславная» метрика — sticky factor, или «липкий фактор» в переводе с английского. Sticky factor отражает регулярность использования сервиса или приложения и для недельной аудитории рассчитывается как DAU/WAU. Для месячной аудитории — DAU/MAU.

In [48]:
sticky_wau = dau_total / wau_total * 100 
print(f'Для недельной  аудитории  - {sticky_wau}.')
Для недельной  аудитории  - user_id    14.285714
dtype: float64.

За неделю количество пользователей, использующие приложение составляет 14%.

Продолжительности сеанса на каждого пользователя¶

In [49]:
# определим начало и конец каждого сеанса каждого пользователя в приложении
# добавляем столбец время в часах
unnecessary_things['event_hour'] = unnecessary_things['time'].dt.hour
# начало сеанса
user_time_min = unnecessary_things.groupby(['user_id', 'data', 'event_hour'], as_index=False)\
.agg({'time':'min'})\
.rename(columns={'time':'event_time_start'})

# конец сеанса
user_time_max = unnecessary_things.groupby(['user_id', 'data', 'event_hour'], as_index=False)\
.agg({'time':'max'})\
.rename(columns={'time':'event_time_stop'})
In [50]:
# объединим столбцы с временем начала и окончания сеанса для пользователей
user_time =  pd.merge(user_time_min, user_time_max, on=['user_id', 'data', 'event_hour'])
user_time
Out[50]:
user_id data event_hour event_time_start event_time_stop
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13 2019-10-07 13:39:45.989359 2019-10-07 13:49:41.716617
1 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 18 2019-10-09 18:33:55.577963 2019-10-09 18:42:22.963948
2 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 19 2019-10-21 19:52:30.778932 2019-10-21 19:57:49.029206
3 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 20 2019-10-21 20:00:00.438922 2019-10-21 20:07:30.051028
4 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 11 2019-10-22 11:18:14.635436 2019-10-22 11:30:52.807203
... ... ... ... ... ...
12427 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-02 18 2019-11-02 18:01:27.094834 2019-11-02 18:17:41.386651
12428 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-02 19 2019-11-02 19:25:53.794029 2019-11-02 19:30:50.471310
12429 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 14 2019-11-03 14:32:55.956301 2019-11-03 14:48:44.263356
12430 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 15 2019-11-03 15:36:01.007440 2019-11-03 15:51:57.899997
12431 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 16 2019-11-03 16:07:40.932077 2019-11-03 16:08:25.388712

12432 rows × 5 columns

In [51]:
# посчитаем, cколько длился сеанс и переведем в минуты
user_time['time_spent'] = (user_time['event_time_stop'] - user_time['event_time_start'])\
.astype('timedelta64[s]') / 60
user_time['time_spent'] = round(user_time['time_spent'], 1)
user_time
Out[51]:
user_id data event_hour event_time_start event_time_stop time_spent
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13 2019-10-07 13:39:45.989359 2019-10-07 13:49:41.716617 9.9
1 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 18 2019-10-09 18:33:55.577963 2019-10-09 18:42:22.963948 8.4
2 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 19 2019-10-21 19:52:30.778932 2019-10-21 19:57:49.029206 5.3
3 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 20 2019-10-21 20:00:00.438922 2019-10-21 20:07:30.051028 7.5
4 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 11 2019-10-22 11:18:14.635436 2019-10-22 11:30:52.807203 12.6
... ... ... ... ... ... ...
12427 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-02 18 2019-11-02 18:01:27.094834 2019-11-02 18:17:41.386651 16.2
12428 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-02 19 2019-11-02 19:25:53.794029 2019-11-02 19:30:50.471310 4.9
12429 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 14 2019-11-03 14:32:55.956301 2019-11-03 14:48:44.263356 15.8
12430 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 15 2019-11-03 15:36:01.007440 2019-11-03 15:51:57.899997 15.9
12431 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 16 2019-11-03 16:07:40.932077 2019-11-03 16:08:25.388712 0.7

12432 rows × 6 columns

In [52]:
# добавим в нее время продолжительности сеанса для каждого пользователя 
taum = user_time.groupby('user_id', as_index=False)\
.agg({'time_spent':'sum', 'data':'count'})\
.rename(columns={'data':'visits'})
taum.head()
Out[52]:
user_id time_spent visits
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 43.7 5
1 00157779-810c-4498-9e05-a1e9e3cedf93 177.5 10
2 00463033-5717-4bf1-91b4-09183923b9df 21.2 2
3 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 87.5 7
4 00551e79-152e-4441-9cf7-565d7eb04090 9.3 3
In [53]:
#  общее время сеанса
taum['time_spent'].sum()
Out[53]:
123671.30000000002
In [54]:
# среднее время сеанса
taum['time_spent'].sum() / taum['user_id'].nunique()
Out[54]:
28.80766363848125

Среднее время продолжительности сеанса 29 минут, округляем - 30 минут. Это время будем использвать для расчeтa тайм-аута.

Вывод исследовательский анализ данных: Всего в данных 74197 событий и 4293 пользователя. В среднем на пользователя приходится 17 событий. Медианное количество при этом составляет 9. Минимальное значение 1, максимальное количество действий 478.

В данных 3 источника:yandex(34286), google(20445), other(19466) , с которых пользователь установил приложение. В течении 28 дней пользователи с разных источников заходили в приложениене. У всех источников были всплески и падения по дням. Больше всего событий проходило из источника yandex(34286). В 1,7 раза меньше google(20445) и other(19466). Заметны снижения активности пользователй в выходные дни.

В представленных данных 9 действий: tips_show(40055), photos_show(10012), search(6784), advert_open(6164), contacts_show(4529), map(3881), favorites_add(1417), tips_click(814), contacts_call(541), который может выполнить пользователь. В каждом действии есть всплески по времени.Особенно сильно заметны всплески у действий: tips_show, tips_click. Больше всего событий у tips_show(40055)54% от общего количества действий, в 4 меньше photos_show(10012)13% и остальные действия составляют 24130 - 33%. Пользователи пользуются приложением в буднии дни. В первых действиях нет contact_call. Количество действий снижается от tips_show до tips_clik. Особенно резкое снижение идет от tips_show(1368) до contacts_show(173). Последнии действия все 9. Количество действий также снижается от tips_show(2403)57% от общего количества пользователей до tips_clik(26)0,6%, но не так резко как первых действиях. Порядок последних действий пользователей меняется по сравнению с первыми действиями.

Через поисковую систему(6394) до просмотра контактов доходит 1311(21%) пользователей. Перед ЦС пользователи просматривают фотографии и открывают карточку объявления.

Через рекомендованное объявление (662) до просмотра контактов доходит 547(83%) пользователей. Пользователю не приходится гулять по приложению, а сразу они просматривают контакты. Если бы все пользователи пользовались рекомендованным объявление, то до Целевого действия доходило бы 83% людей. При поиске по сайту доходят 21% пользователей , т.к. путь пользователя удлиняется и на каждом этапе происходит отток людей.

Минимальная дата: 2019-10-07 00:00:00.431357. Максимальная дата: 2019-11-03 23:58:12.532487. Период исследования составляет практически 28 дней без 2 минут.

Retention Rate коэффициент удержания низкий 24%, значит 76% отваливаются. С каждой новой когортой падает коэффициент удержания.

Churn Rate коэффициент отскока высокий 76%. С каждой новой когортой увеливается коэффициент отскока. Что-то не устраивает в приложении пользователей.

Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073. Среднее время продолжительности сеанса 29 минут, округляем - 30 минут. Это время будем использвать для расчете тайм-аута.

Шаг Анализ событий¶

Создаем сессии по пользователям¶

Выделяем сессии относительно тайм-аута.

Тайм-аут визита — это время бездействия посетителя на сайте, после которого визит считается завершенным. По умолчанию тайм-аут визита составляет 30 минут. Поэтому если в отчетах счетчика присутствует большое количество внутренних переходов, и при этом на сайте есть страницы, на которых пользователи могли бы остаться более чем нам 30 минут, то необходимо увеличить время тайм-аута визита. Максимальное значение тайм-аута составляет 360 минут. Однако помните, что без надобности менять тайм-аут визита не требуется, иначе есть риск получить искаженную статистику. https://yandex.ru/blog/metrika-club/gid-po-metrike-nastroyki-schetchika

In [55]:
# преобразовываем  дату и время
unnecessary_things['time'] = pd.to_datetime(unnecessary_things['time'])
# определяем разницу за 30 минут для каждой группы с суммарной суммой
unnecessary_things1 = unnecessary_things.sort_values(by=['user_id', 'time'])
g = (unnecessary_things1.groupby('user_id')['time'].diff() > pd.Timedelta('30Min')).cumsum()
#создаем счетчик групп
unnecessary_things1['session_id'] = unnecessary_things1.groupby(['user_id', g], sort=False).ngroup() + 1
unnecessary_things1#.head(100)
Out[55]:
user_id source time event data event_hour session_id
2171 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:39:45.989359 tips_show 2019-10-07 13 1
2172 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:40:31.052909 tips_show 2019-10-07 13 1
2173 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:41:05.722489 tips_show 2019-10-07 13 1
2174 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:43:20.735461 tips_show 2019-10-07 13 1
2175 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:45:30.917502 tips_show 2019-10-07 13 1
... ... ... ... ... ... ... ...
19048 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:23.959572 tips_show 2019-11-03 15 10368
19049 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:57.899997 contacts_show 2019-11-03 15 10368
19050 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:07:40.932077 tips_show 2019-11-03 16 10368
19051 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:18.202734 tips_show 2019-11-03 16 10368
19052 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:25.388712 tips_show 2019-11-03 16 10368

74197 rows × 7 columns

In [56]:
# проверяем на дубликаты
unnecessary_things1[unnecessary_things1.duplicated(subset=['session_id','event'])]
Out[56]:
user_id source time event data event_hour session_id
2172 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:40:31.052909 tips_show 2019-10-07 13 1
2173 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:41:05.722489 tips_show 2019-10-07 13 1
2174 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:43:20.735461 tips_show 2019-10-07 13 1
2175 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:45:30.917502 tips_show 2019-10-07 13 1
2176 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:45:43.212340 tips_show 2019-10-07 13 1
... ... ... ... ... ... ... ...
19048 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:23.959572 tips_show 2019-11-03 15 10368
19049 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:51:57.899997 contacts_show 2019-11-03 15 10368
19050 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:07:40.932077 tips_show 2019-11-03 16 10368
19051 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:18.202734 tips_show 2019-11-03 16 10368
19052 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 16:08:25.388712 tips_show 2019-11-03 16 10368

56343 rows × 7 columns

In [57]:
# удаляем дубликаты
unnecessary_things1=unnecessary_things1.drop_duplicates(subset=['session_id','event'])
unnecessary_things1
Out[57]:
user_id source time event data event_hour session_id
2171 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-07 13:39:45.989359 tips_show 2019-10-07 13 1
2180 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-09 18:33:55.577963 map 2019-10-09 18 2
2182 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-09 18:40:28.738785 tips_show 2019-10-09 18 2
2184 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-21 19:52:30.778932 tips_show 2019-10-21 19 3
2186 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 other 2019-10-21 19:53:38.767230 map 2019-10-21 19 3
... ... ... ... ... ... ... ...
19021 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-02 19:26:07.834494 contacts_show 2019-11-02 19 10366
19024 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 14:32:55.956301 tips_show 2019-11-03 14 10367
19025 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 14:33:47.921863 contacts_show 2019-11-03 14 10367
19039 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:36:01.007440 tips_show 2019-11-03 15 10368
19044 fffb9e79-b927-4dbb-9b48-7fd09b23a62b google 2019-11-03 15:48:05.420247 contacts_show 2019-11-03 15 10368

17854 rows × 7 columns

In [58]:
unnecessary_things['event'].value_counts()
Out[58]:
tips_show        40055
photos_show      10012
search            6784
advert_open       6164
contacts_show     4529
map               3881
favorites_add     1417
tips_click         814
contacts_call      541
Name: event, dtype: int64
In [59]:
# количество сессий
unnecessary_things1['session_id'].nunique()
Out[59]:
10368
In [60]:
# количество пользователей
unnecessary_things1['user_id'].nunique()
Out[60]:
4293
In [61]:
# количество сессий на пользователя
unnecessary_things1['session_id'].nunique()/unnecessary_things1['user_id'].nunique()
Out[61]:
2.4150943396226414
In [62]:
# количество событий
len(unnecessary_things1)
Out[62]:
17854
In [63]:
# количество сессий на событие
len(unnecessary_things1)/unnecessary_things1['session_id'].nunique()
Out[63]:
1.7220293209876543

Всего получилось 10368 сессий.На каждого пользователя приходится 2 сессии. На каждую сессию приходится по 2 события.

Строим диаграмму Санкея¶

In [64]:
def add_features(df):
    
    """Функция генерации новых столбцов для исходной таблицы

    Args:
        df (pd.DataFrame): исходная таблица.
    Returns:
        pd.DataFrame: таблица с новыми признаками.
    """
    
    # сортируем по id и времени
    sorted_df = df.sort_values(by=['session_id', 'time']).copy()
    # добавляем шаги событий
    sorted_df['step'] = sorted_df.groupby('session_id').cumcount() + 1
    
    # добавляем узлы-источники и целевые узлы
    # узлы-источники - это сами события
    sorted_df['source'] = sorted_df['event']
    # добавляем целевые узлы
    sorted_df['target'] = sorted_df.groupby('session_id')['source'].shift(-1)
    
    # возврат таблицы без имени событий
    return sorted_df.drop(['event'], axis=1)
  
# преобразуем таблицу
table = add_features(unnecessary_things1)
table.head(10)
Out[64]:
user_id source time data event_hour session_id step target
2171 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 tips_show 2019-10-07 13:39:45.989359 2019-10-07 13 1 1 NaN
2180 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 map 2019-10-09 18:33:55.577963 2019-10-09 18 2 1 tips_show
2182 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 tips_show 2019-10-09 18:40:28.738785 2019-10-09 18 2 2 NaN
2184 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 tips_show 2019-10-21 19:52:30.778932 2019-10-21 19 3 1 map
2186 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 map 2019-10-21 19:53:38.767230 2019-10-21 19 3 2 NaN
2198 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 map 2019-10-22 11:18:14.635436 2019-10-22 11 4 1 tips_show
2199 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 tips_show 2019-10-22 11:19:10.529462 2019-10-22 11 4 2 NaN
42960 00157779-810c-4498-9e05-a1e9e3cedf93 search 2019-10-19 21:34:33.849769 2019-10-19 21 5 1 photos_show
42963 00157779-810c-4498-9e05-a1e9e3cedf93 photos_show 2019-10-19 21:40:38.990477 2019-10-19 21 5 2 NaN
42969 00157779-810c-4498-9e05-a1e9e3cedf93 search 2019-10-20 18:49:24.115634 2019-10-20 18 6 1 photos_show
In [65]:
table['step'].nunique()
Out[65]:
6
In [66]:
table['source'].value_counts()
Out[66]:
tips_show        6035
search           2974
photos_show      2526
map              2133
contacts_show    1703
advert_open      1254
favorites_add     501
tips_click        416
contacts_call     312
Name: source, dtype: int64
In [67]:
table.groupby('session_id')['step'].count().describe()
Out[67]:
count    10368.000000
mean         1.722029
std          0.893418
min          1.000000
25%          1.000000
50%          1.000000
75%          2.000000
max          6.000000
Name: step, dtype: float64
In [68]:
# считаем количество сессий по категориям
count_session_id = table.groupby("session_id").agg({"source": lambda x: tuple(x)}).reset_index()#.head(100)
count_session_id5 = count_session_id.groupby('source').agg({'session_id': 'nunique'}).reset_index().sort_values(by = 'session_id', ascending=False)
count_session_id5.head()
Out[68]:
source session_id
295 (tips_show,) 2677
171 (photos_show,) 1320
202 (search,) 762
148 (map, tips_show) 547
247 (search, photos_show) 524
In [69]:
# смотрим сколько действий по шагам
dff1 = table.groupby('step').agg({'source': ['unique',  'count']})#.reset_index()#.sort_values(by = 'count', ascending=False).head(60)
# преобразовываем MultiIndex в Index
dff1.columns = dff1.columns.map(''.join)
dff1.rename(columns={'sourceunique': 'event', 'sourcecount': 'count'}, inplace = True)
dff1.reset_index()#.sort_values(by = 'count', ascending=False).head(60)
Out[69]:
step event count
0 1 [tips_show, map, search, photos_show, contacts... 10368
1 2 [tips_show, map, photos_show, advert_open, con... 5076
2 3 [favorites_add, contacts_show, map, contacts_c... 1818
3 4 [contacts_show, contacts_call, advert_open, ti... 503
4 5 [contacts_call, favorites_add, map, advert_ope... 83
5 6 [advert_open, search, contacts_call] 6
In [70]:
dff = table.groupby('session_id').agg({'source': ['unique',  'count']})
# преобразовываем MultiIndex в Index
dff.columns = dff.columns.map(''.join)
dff.rename(columns={'sourceunique': 'event', 'sourcecount': 'count'}, inplace = True)
dff.reset_index().head()
Out[70]:
session_id event count
0 1 [tips_show] 1
1 2 [map, tips_show] 2
2 3 [tips_show, map] 2
3 4 [map, tips_show] 2
4 5 [search, photos_show] 2
In [71]:
# ограничемся з шагами, при среднем значении 2 

df_comp = table[table['step'] <= 3].copy().reset_index(drop=True)
In [72]:
PATH_TO_CSV = 'https://raw.githubusercontent.com/rusantsovsv/senkey_tutorial/main/csv/senkey_data_tutorial.csv'
In [73]:
def get_source_index(df):
    
    """Функция генерации индексов source

    Args:
        df (pd.DataFrame): исходная таблица с признаками step, source, target.
    Returns:
        dict: словарь с индексами, именами и соответсвиями индексов именам source.
    """
    
    res_dict = {}
    
    count = 0
    # получаем индексы источников
    for no, step in enumerate(df['step'].unique().tolist()):
        # получаем уникальные наименования для шага
        res_dict[no+1] = {}
        res_dict[no+1]['sources'] = df[df['step'] == step]['source'].unique().tolist()
        res_dict[no+1]['sources_index'] = []
        for i in range(len(res_dict[no+1]['sources'])):
            res_dict[no+1]['sources_index'].append(count)
            count += 1
            
    # соединим списки
    for key in res_dict:
        res_dict[key]['sources_dict'] = {}
        for name, no in zip(res_dict[key]['sources'], res_dict[key]['sources_index']):
            res_dict[key]['sources_dict'][name] = no
    return res_dict
  

# создаем словарь
source_indexes = get_source_index(df_comp)
In [74]:
def generate_random_color():
    
    """Случайная генерация цветов rgba

    Args:
        
    Returns:
        str: Строка со сгенерированными параметрами цвета
    """
    
    # сгенерим значение для каждого канала
    r, g, b = np.random.randint(255, size=3)
    return f'rgba({r}, {g}, {b}, 1)'
In [75]:
def colors_for_sources(mode):
    
    """Генерация цветов rgba

    Args:
        mode (str): сгенерировать случайные цвета, если 'random', а если 'custom' - 
                    использовать заранее подготовленные
    Returns:
        dict: словарь с цветами, соответствующими каждому индексу
    """
    # словарь, в который сложим цвета в соответствии с индексом
    colors_dict = {}
    
    if mode == 'random':
        # генерим случайные цвета
        for label in df_comp['source'].unique():
            r, g, b = np.random.randint(255, size=3)            
            colors_dict[label] = f'rgba({r}, {g}, {b}, 1)'
            
    elif mode == 'custom':
        # присваиваем ранее подготовленные цвета
        colors = requests.get('https://raw.githubusercontent.com/rusantsovsv/senkey_tutorial/main/json/colors_senkey.json').json()
        for no, label in enumerate(df_comp['source'].unique()):
            colors_dict[label] = colors['custom_colors'][no]
            
    return colors_dict
In [76]:
colors_dict = colors_for_sources(mode='custom')
In [77]:
def percent_users(sources, targets, values):
    
    """
    Расчет уникальных id в процентах (для вывода в hover text каждого узла)
    
    Args:
        sources (list): список с индексами source.
        targets (list): список с индексами target.
        values (list): список с "объемами" потоков.
        
    Returns:
        list: список с "объемами" потоков в процентах
    """
    
    # объединим источники и метки и найдем пары
    zip_lists = list(zip(sources, targets, values))
    
    new_list = []
    
    # подготовим список словарь с общим объемом трафика в узлах
    unique_dict = {}
    
    # проходим по каждому узлу
    for source, target, value in zip_lists:
        if source not in unique_dict:
            # находим все источники и считаем общий трафик
            unique_dict[source] = 0
            for sr, tg, vl in zip_lists:
                if sr == source:
                    unique_dict[source] += vl
                    
    # считаем проценты
    for source, target, value in zip_lists:
        new_list.append(round(100 * value / unique_dict[source], 1))
    
    return new_list
In [78]:
# Создание словаря с данными для отрисовки диаграммы
def lists_for_plot(source_indexes=source_indexes, colors=colors_dict, frac=10):
    
    """
    Создаем необходимые для отрисовки диаграммы переменные списков и возвращаем
    их в виде словаря
    
    Args:
        source_indexes (dict): словарь с именами и индексами source.
        colors (dict): словарь с цветами source.
        frac (int): ограничение на минимальный "объем" между узлами.
        
    Returns:
        dict: словарь со списками, необходимыми для диаграммы.
    """
    
    sources = []
    targets = []
    values = []
    labels = []
    link_color = []
    link_text = []

    # проходим по каждому шагу
    for step in tqdm(sorted(df_comp['step'].unique()), desc='Шаг'):
        if step + 1 not in source_indexes:
            continue

        # получаем индекс источника
        temp_dict_source = source_indexes[step]['sources_dict']

        # получаем индексы цели
        temp_dict_target = source_indexes[step+1]['sources_dict']

        # проходим по каждой возможной паре, считаем количество таких пар
        for source, index_source in tqdm(temp_dict_source.items()):
            for target, index_target in temp_dict_target.items():
                # делаем срез данных и считаем количество id            
                temp_df = df_comp[(df_comp['step'] == step)&(df_comp['source'] == source)&(df_comp['target'] == target)]
                value = len(temp_df)
                # проверяем минимальный объем потока и добавляем нужные данные
                if value > frac:
                    sources.append(index_source)
                    targets.append(index_target)
                    values.append(value)
                    # делаем поток прозрачным для лучшего отображения
                    link_color.append(colors[source].replace(', 1)', ', 0.2)'))
                    
    labels = []
    colors_labels = []
    for key in source_indexes:
        for name in source_indexes[key]['sources']:
            labels.append(name)
            colors_labels.append(colors[name])
            
    # посчитаем проценты всех потоков
    perc_values = percent_users(sources, targets, values)
    
    # добавим значения процентов для howertext
    link_text = []
    for perc in perc_values:
        link_text.append(f"{perc}%")
    
    # возвратим словарь с вложенными списками
    return {'sources': sources, 
            'targets': targets, 
            'values': values, 
            'labels': labels, 
            'colors_labels': colors_labels, 
            'link_color': link_color, 
            'link_text': link_text}
  

# создаем словарь
data_for_plot = lists_for_plot()
Шаг:   0%|          | 0/3 [00:00<?, ?it/s]
  0%|          | 0/8 [00:00<?, ?it/s]
  0%|          | 0/9 [00:00<?, ?it/s]
In [79]:
def plot_senkey_diagram(data_dict=data_for_plot):    
    
    """
    Функция для генерации объекта диаграммы Сенкей 
    
    Args:
        data_dict (dict): словарь со списками данных для построения.
        
    Returns:
        plotly.graph_objs._figure.Figure: объект изображения.
    """
    
    fig = go.Figure(data=[go.Sankey(
        domain = dict(
          x =  [0,1],
          y =  [0,1]
        ),
        orientation = "h",
        valueformat = ".0f",
        node = dict(
          pad = 50,
          thickness = 15,
          line = dict(color = "black", width = 0.1),
          label = data_dict['labels'],
          color = data_dict['colors_labels']
        ),
        link = dict(
          source = data_dict['sources'],
          target = data_dict['targets'],
          value = data_dict['values'],
          label = data_dict['link_text'],
          color = data_dict['link_color']
      ))])
    fig.update_layout(title_text="Sankey Diagram", font_size=10, width=1100, height=700)
    
    # возвращаем объект диаграммы
    return fig
  

# сохраняем диаграмму в переменную
senkey_diagram = plot_senkey_diagram()
In [80]:
senkey_diagram.show()

Строим воронки по выбранным сценариям¶

Построим воронку по пользователям, которые пришли в просмотр контактов через открытие карты объявления

In [81]:
# выбираем уникальных пользователей в датасете по map(открыл карту объявления)
map_event = unnecessary_things.query('event == "map"')
map_uniq = map_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
map_user = unnecessary_things.query('user_id == @ map_uniq')
# выбираем события , по которым будем строить воронку
map_tips_show_contacts_show = map_user.query('event == "map" or event == "tips_show" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_map = map_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'})\
                                        .sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_map['conversion'] = round(funnel_map['user_id']/ funnel_map['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_map['funnel'] =  round(funnel_map['user_id'] / funnel_map['user_id'].shift(1)*100,2)
funnel_map
Out[81]:
event user_id conversion funnel
0 map 1456 47.01 NaN
1 tips_show 1352 43.66 92.86
2 contacts_show 289 9.33 21.38
In [82]:
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
    x = funnel_map['user_id'],
    y = funnel_map['event'],
    opacity = 0.6,
    textposition = "auto",
    textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через открытие карты объявления', title_x = 0.5)
fig.show()

После открытия карты объявления, увидели рекомендованное объявление 93% пользователей. И 21 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.

Построим воронку по пользователям, которые пришли в просмотр контактов через открытие карточки объявления

In [83]:
# выбираем уникальных пользователей в датасете по advert_open(открыл карточку объявления)
advert_open_event = unnecessary_things.query('event == "advert_open"')
advert_open_uniq = advert_open_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
advert_open_user = unnecessary_things.query('user_id == @ advert_open_uniq')
# выбираем события , по которым будем строить воронку
advert_open_tips_show_contacts_show = advert_open_user\
                                     .query('event == "tips_show" or event == "advert_open" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_advert_open = advert_open_tips_show_contacts_show.groupby('event')\
                     .agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_advert_open['conversion'] = round(funnel_advert_open['user_id']/ funnel_advert_open['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_advert_open['funnel'] =  round(funnel_advert_open['user_id'] / funnel_advert_open['user_id'].shift(1)*100,2)
funnel_advert_open
Out[83]:
event user_id conversion funnel
0 advert_open 751 50.78 NaN
1 tips_show 590 39.89 78.56
2 contacts_show 138 9.33 23.39
In [84]:
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
    x = funnel_advert_open['user_id'],
    y = funnel_advert_open['event'],
    opacity = 0.6,
    textposition = "auto",
    textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через открытие карточки объявления', title_x = 0.5)
fig.show()

После открытия карточки объявления, увидели рекомендованные объявления 79% пользователей. И 23 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.

Построим воронку по пользователям, которые пришли в просмотр контактов через поиск по приложению

In [85]:
# выбираем уникальных пользователей в датасете по search(поиск по сайту)
search_event = unnecessary_things.query('event == "search"')
search_uniq = search_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
search_user = unnecessary_things.query('user_id == @ search_uniq')
# выбираем события , по которым будем строить воронку
search_tips_show_contacts_show = search_user.query('event == "search" or event == "tips_show"or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_search = search_tips_show_contacts_show.groupby('event')\
                                              .agg({'user_id': 'nunique'})\
                                              .sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_search['conversion'] = round(funnel_search['user_id']/ funnel_search['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_search['funnel'] =  round(funnel_search['user_id'] / funnel_search['user_id'].shift(1)*100,2)
funnel_search
Out[85]:
event user_id conversion funnel
0 search 1666 58.58 NaN
1 tips_show 801 28.16 48.08
2 contacts_show 377 13.26 47.07
In [86]:
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
    x = funnel_search['user_id'],
    y = funnel_search['event'],
    opacity = 0.6,
    textposition = "auto",
    textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через поиск по приложению', title_x = 0.5)
fig.show()

После поиска по сайту, увидели рекомендованные объявления 48% пользователей. И 47 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.

Рассмотрели 3 сценария:

1.search(100%)-tips_show(48%)-contacts_show(47%).

2.advert_open(100%) - tips_show(79%)-contacts_show(23%)

3.map(100%) - tips_show(93%)-contacts_show(21%).

Выше всего конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.

На втором месте конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.

На третьем месте конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.

Рассчет среднего времени от первого действия до Целевого¶

In [87]:
# создаем таблицу с первыми данными по contacts_show
session_id_contacts_show = table.sort_values(by=['session_id', 'time'])
session_id_contacts_show = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
session_id_contacts_show.columns = ['session_id', 'time_first_contacts_show']
session_id_contacts_show.head()
Out[87]:
session_id time_first_contacts_show
0 6 2019-10-20 19:17:18.659799
1 8 2019-10-29 21:26:40.258472
2 9 2019-10-30 08:01:05.420773
3 10 2019-11-03 17:12:09.708771
4 18 2019-10-25 16:44:41.263364
In [88]:
# создаем данные по первому действию
session_id_event_first_time_min = table.sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец 
session_id_event_first_time_min =session_id_event_first_time_min.groupby('session_id').agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min.columns = session_id_event_first_time_min.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_event_first_time_min.reset_index().head()
Out[88]:
session_id time_min event_first
0 1 2019-10-07 13:39:45.989359 tips_show
1 2 2019-10-09 18:33:55.577963 map
2 3 2019-10-21 19:52:30.778932 tips_show
3 4 2019-10-22 11:18:14.635436 map
4 5 2019-10-19 21:34:33.849769 search
In [89]:
# соединяем таблицы с первыми данными по contacts_show и данные по первому действию
session_id_contacts_show_first = session_id_contacts_show.merge(session_id_event_first_time_min, on='session_id', how='left')
session_id_contacts_show_first.head()
Out[89]:
session_id time_first_contacts_show time_min event_first
0 6 2019-10-20 19:17:18.659799 2019-10-20 18:49:24.115634 search
1 8 2019-10-29 21:26:40.258472 2019-10-29 21:18:24.850073 search
2 9 2019-10-30 08:01:05.420773 2019-10-30 07:50:45.948358 search
3 10 2019-11-03 17:12:09.708771 2019-11-03 17:12:09.708771 contacts_show
4 18 2019-10-25 16:44:41.263364 2019-10-25 16:44:41.263364 contacts_show
In [90]:
# считаем количество дней 
session_id_contacts_show_first['delta_time'] = session_id_contacts_show_first['time_first_contacts_show'] - session_id_contacts_show_first['time_min']
session_id_contacts_show_first.sort_values(by='delta_time').head()
Out[90]:
session_id time_first_contacts_show time_min event_first delta_time
1568 9616 2019-10-27 12:34:17.079917 2019-10-27 12:34:17.079917 contacts_show 0 days
591 3786 2019-10-09 13:38:51.658132 2019-10-09 13:38:51.658132 contacts_show 0 days
1156 7168 2019-10-13 04:22:31.084013 2019-10-13 04:22:31.084013 contacts_show 0 days
594 3818 2019-10-14 19:14:18.179125 2019-10-14 19:14:18.179125 contacts_show 0 days
1459 9088 2019-10-10 13:31:12.999336 2019-10-10 13:31:12.999336 contacts_show 0 days
In [91]:
# считаем количество пустых значений(contacts_show)
session_id_contacts_show_first.query('delta_time == "0 days 00:00:00"').count()
Out[91]:
session_id                  543
time_first_contacts_show    543
time_min                    543
event_first                 543
delta_time                  543
dtype: int64
In [92]:
# удалаем пустые значения
session_id_contacts_show_first = session_id_contacts_show_first.query('delta_time != "0 days 00:00:00"')
session_id_contacts_show_first.head()
Out[92]:
session_id time_first_contacts_show time_min event_first delta_time
0 6 2019-10-20 19:17:18.659799 2019-10-20 18:49:24.115634 search 0 days 00:27:54.544165
1 8 2019-10-29 21:26:40.258472 2019-10-29 21:18:24.850073 search 0 days 00:08:15.408399
2 9 2019-10-30 08:01:05.420773 2019-10-30 07:50:45.948358 search 0 days 00:10:19.472415
5 19 2019-10-28 13:10:40.331441 2019-10-28 13:08:15.809056 search 0 days 00:02:24.522385
8 30 2019-10-22 13:08:09.140381 2019-10-22 13:02:26.636223 map 0 days 00:05:42.504158
In [93]:
session_id_contacts_show_first.describe()
Out[93]:
session_id delta_time
count 1160.000000 1160
mean 5403.843966 0 days 00:09:45.569282692
std 2969.959565 0 days 00:13:38.461736017
min 6.000000 0 days 00:00:00.158918
25% 2936.750000 0 days 00:01:23.731704750
50% 5564.500000 0 days 00:04:51.950202500
75% 7882.250000 0 days 00:12:20.300075250
max 10368.000000 0 days 03:21:08.091077

Среднее время от первого действия до Целового составляет 10 минут.

In [94]:
# переведем дни в минуты
session_id_contacts_show_first['minutes'] = session_id_contacts_show_first['delta_time']/np.timedelta64(1,'m')
session_id_contacts_show_first
Out[94]:
session_id time_first_contacts_show time_min event_first delta_time minutes
0 6 2019-10-20 19:17:18.659799 2019-10-20 18:49:24.115634 search 0 days 00:27:54.544165 27.909069
1 8 2019-10-29 21:26:40.258472 2019-10-29 21:18:24.850073 search 0 days 00:08:15.408399 8.256807
2 9 2019-10-30 08:01:05.420773 2019-10-30 07:50:45.948358 search 0 days 00:10:19.472415 10.324540
5 19 2019-10-28 13:10:40.331441 2019-10-28 13:08:15.809056 search 0 days 00:02:24.522385 2.408706
8 30 2019-10-22 13:08:09.140381 2019-10-22 13:02:26.636223 map 0 days 00:05:42.504158 5.708403
... ... ... ... ... ... ...
1697 10360 2019-10-29 16:13:00.681459 2019-10-29 16:12:15.417343 tips_show 0 days 00:00:45.264116 0.754402
1699 10365 2019-11-02 18:17:41.386651 2019-11-02 18:01:27.094834 tips_show 0 days 00:16:14.291817 16.238197
1700 10366 2019-11-02 19:26:07.834494 2019-11-02 19:25:53.794029 tips_show 0 days 00:00:14.040465 0.234008
1701 10367 2019-11-03 14:33:47.921863 2019-11-03 14:32:55.956301 tips_show 0 days 00:00:51.965562 0.866093
1702 10368 2019-11-03 15:48:05.420247 2019-11-03 15:36:01.007440 tips_show 0 days 00:12:04.412807 12.073547

1160 rows × 6 columns

In [95]:
session_id_contacts_show_first.describe()
Out[95]:
session_id delta_time minutes
count 1160.000000 1160 1160.000000
mean 5403.843966 0 days 00:09:45.569282692 9.759488
std 2969.959565 0 days 00:13:38.461736017 13.641029
min 6.000000 0 days 00:00:00.158918 0.002649
25% 2936.750000 0 days 00:01:23.731704750 1.395528
50% 5564.500000 0 days 00:04:51.950202500 4.865837
75% 7882.250000 0 days 00:12:20.300075250 12.338335
max 10368.000000 0 days 03:21:08.091077 201.134851
In [96]:
# среднее время по действиям
session_id_contacts_show_first.groupby('event_first').agg({'minutes': 'mean'})
Out[96]:
minutes
event_first
advert_open 4.794377
favorites_add 6.352063
map 10.617769
photos_show 10.121990
search 8.656574
tips_click 26.086436
tips_show 10.388056
In [97]:
# строим распределение действий по среднему времени
session_id_contacts_show_first.groupby('event_first').agg({'minutes': 'mean'}).sort_values(by='minutes', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение от первого действия до Целового по среднему времени')
plt.xlabel('Действия')
plt.ylabel('время')
plt.show();

Количество сессий на среднее время

In [98]:
# формируем таблицу по медианному  времени и количеству уникальных сессий
session_id_average_time = session_id_contacts_show_first.groupby('event_first')\
                                                        .agg({'minutes': ['mean'], 'session_id':['nunique']})
# преобразовываем MultiIndex в Index
session_id_average_time.columns = session_id_average_time.columns.map(''.join)
# меняем название столбцов
session_id_average_time.rename(columns={'minutesmean': 'mean', 'session_idnunique': 'session_id_count'}, inplace = True)
session_id_average_time.reset_index()
session_id_average_time.sort_values(by='mean', ascending=False)
#session_id_average_time
Out[98]:
mean session_id_count
event_first
tips_click 26.086436 3
map 10.617769 178
tips_show 10.388056 486
photos_show 10.121990 156
search 8.656574 280
favorites_add 6.352063 18
advert_open 4.794377 39
In [99]:
# общее среднее время сессий от первого до Целевого по действиям
session_id_average_time['mean'].sum()
Out[99]:
77.01726328016005

Среднее время от первого действия до Целового составляет 10 минут.Чтобы дойти до Целевого действия больше всего времени уходит на tips_click(клик по рекомендованному объявлению) 26 минут. На map(открыл карту объявления) , tips_show(увидел рекомендованное объявление) и photos_show(посмотрел фотографии объявлений) тратится 10 минут. Меньше всего времени уходит advert_open(открыл карточки объявления)- 5 минут. Почему так долго пользователь находится в tips_click?

Проанализируй связь целевого события - просмотр контактов - и другие действия¶

In [100]:
# создаем таблицу с первыми данными по contacts_show
first_contacts_show = mobile_dataset.query('event == "contacts_show"').groupby('user_id')['time'].min().reset_index()
first_contacts_show.columns = ['user_id', 'time_first_contacts_show']
first_contacts_show.head()
Out[100]:
user_id time_first_contacts_show
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381
In [101]:
# создаем данные по первому действию
event_first_time_min = mobile_dataset.sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец 
event_first_time_min =event_first_time_min.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
event_first_time_min.columns = event_first_time_min.columns.map(''.join)
# меняем название столбцов
event_first_time_min.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
event_first_time_min.reset_index().head()
Out[101]:
user_id time_min event_first
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13:39:45.989359 tips_show
1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 21:34:33.849769 search
2 00463033-5717-4bf1-91b4-09183923b9df 2019-11-01 13:54:35.385028 photos_show
3 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-18 22:14:05.555052 search
4 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 contacts_show
In [102]:
# соединяем таблицы с первыми данными по contacts_show и данные по первому действию
user_id_contacts_show_first = first_contacts_show.merge(event_first_time_min, on='user_id', how='left')
user_id_contacts_show_first
Out[102]:
user_id time_first_contacts_show time_min event_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 2019-10-19 21:34:33.849769 search
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 2019-10-25 16:44:41.263364 contacts_show
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841 2019-10-11 11:22:54.442841 contacts_show
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501 2019-10-20 14:57:06.080501 contacts_show
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 2019-10-22 13:02:26.636223 map
... ... ... ... ...
976 fee3ba1c-16f4-46f7-bf56-4bf80cc4e2f5 2019-10-26 12:13:19.781554 2019-10-26 11:43:11.921954 search
977 ff1554b5-919e-40b1-90bb-ee1f7f6d5846 2019-10-21 10:59:23.944891 2019-10-21 08:28:33.429234 photos_show
978 ffc01466-fdb1-4460-ae94-e800f52eb136 2019-10-07 20:33:42.135500 2019-10-07 20:32:49.997044 photos_show
979 ffe68f10-e48e-470e-be9b-eeb93128ff1a 2019-10-22 16:07:17.683553 2019-10-21 16:39:33.867145 search
980 fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-10-16 12:57:40.598641 2019-10-12 00:57:21.241896 tips_show

981 rows × 4 columns

In [103]:
# считаем количество дней 
user_id_contacts_show_first['delta_time'] = user_id_contacts_show_first['time_first_contacts_show'] - user_id_contacts_show_first['time_min']
user_id_contacts_show_first.sort_values(by='delta_time').head()
Out[103]:
user_id time_first_contacts_show time_min event_first delta_time
302 4e62e09e-ef99-4c52-b8f6-ec50b42f6d56 2019-10-16 12:53:01.602333 2019-10-16 12:53:01.602333 contacts_show 0 days
480 7f38ea5d-ad7c-4e8a-b1e1-c63da89a4c4c 2019-10-16 17:07:15.556563 2019-10-16 17:07:15.556563 contacts_show 0 days
198 2fb6515e-a902-4329-8080-749bcf4f8053 2019-10-31 00:35:18.082028 2019-10-31 00:35:18.082028 contacts_show 0 days
200 30a6b55e-a72a-40bc-bbb9-7cc28a2e0b3e 2019-10-11 14:03:59.819208 2019-10-11 14:03:59.819208 contacts_show 0 days
474 7cc38f4c-7d5e-4dd4-a246-ef1ee2187da5 2019-10-19 15:18:30.743570 2019-10-19 15:18:30.743570 contacts_show 0 days
In [104]:
# считаем количество пустых значений(contacts_show)
user_id_contacts_show_first.query('delta_time == "0 days 00:00:00"').count()
Out[104]:
user_id                     179
time_first_contacts_show    179
time_min                    179
event_first                 179
delta_time                  179
dtype: int64
In [105]:
# удалаем пустые значения
user_id_contacts_show_first = user_id_contacts_show_first.query('delta_time != "0 days 00:00:00"')
user_id_contacts_show_first.head()
Out[105]:
user_id time_first_contacts_show time_min event_first delta_time
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 2019-10-19 21:34:33.849769 search 0 days 21:42:44.810030
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 2019-10-22 13:02:26.636223 map 0 days 00:05:42.504158
6 0103a07d-513f-42b9-8d91-d5891d5655fe 2019-11-03 18:50:04.133976 2019-11-03 18:42:59.275533 tips_show 0 days 00:07:04.858443
7 01556e76-d389-43bd-9fc7-1a3ba9802f49 2019-10-10 16:09:38.534928 2019-10-10 16:09:12.170942 photos_show 0 days 00:00:26.363986
8 01585246-7d9b-4e03-a465-095d5b891b0a 2019-10-17 16:21:56.290598 2019-10-17 16:20:58.368137 search 0 days 00:00:57.922461
In [106]:
# количество действий до просмотра контактов
user_id_contacts_show_first.groupby('event_first').agg({'user_id': 'nunique'})\
.sort_values(by='user_id', ascending=False).reset_index()
Out[106]:
event_first user_id
0 tips_show 260
1 search 209
2 photos_show 162
3 map 140
4 favorites_add 17
5 advert_open 12
6 tips_click 2
In [107]:
# строим распределение количество событий до ЦС
user_id_contacts_show_first.groupby('event_first').agg({'user_id': 'count'}).sort_values(by='user_id', ascending=False)\
.plot(figsize=(15, 5), grid=True)#.reset_index().plot(figsize=(15, 5), grid=True)
plt.title('Распределение количество событий до ЦС')
plt.xlabel('Действия')
plt.ylabel('Количество')
plt.show();

До просмотра контактов пользователь совершает следующие действия: tips_show(увидел рекомендованное объявление)- 260, search(выполнил поиск по сайту) - 209, photos_show(посмотрел фотографии) - 162, map(отpкрыл карту объявлений) - 140, favorites_add (добавил объявление в избранное) - 17, advert_open(открыл карточку объявлений) - 12 и затем tips_click(кликнул по рекомендованному объвлению) - 2.

События ADVERT_OPEN -> CONTACTS_SHOW¶

Считаем время проведенное между событиями ADVERT_OPEN -> CONTACTS_SHOW¶
In [108]:
# создаем таблицу с первыми данными по contacts_show и advert_open
advert_open_contacts_show = table.sort_values(by=['session_id', 'time'])
advert_open_contacts_show = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
advert_open_contacts_show.columns = ['session_id', 'time_first']
advert_open_contacts_show.head()
Out[108]:
session_id time_first
0 6 2019-10-20 19:17:18.659799
1 8 2019-10-29 21:26:40.258472
2 9 2019-10-30 08:01:05.420773
3 10 2019-11-03 17:12:09.708771
4 18 2019-10-25 16:44:41.263364
In [109]:
# создаем данные по первому действию advert_open и contacts_show 
session_id_event_first_time_min1 = table.query('source == "advert_open" or source == "contacts_show"').sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец 
session_id_event_first_time_min1 =session_id_event_first_time_min1.groupby('session_id').agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min1.columns = session_id_event_first_time_min1.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min1.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_event_first_time_min1.reset_index().head()
Out[109]:
session_id time_min event_first
0 6 2019-10-20 19:17:18.659799 contacts_show
1 7 2019-10-24 10:52:18.644065 advert_open
2 8 2019-10-29 21:26:40.258472 contacts_show
3 9 2019-10-30 08:01:05.420773 contacts_show
4 10 2019-11-03 17:12:09.708771 contacts_show
In [110]:
# соединяем таблицы 
advert_open_contacts_show_min = advert_open_contacts_show.merge(session_id_event_first_time_min1, on='session_id', how='left')
advert_open_contacts_show_min.head()
Out[110]:
session_id time_first time_min event_first
0 6 2019-10-20 19:17:18.659799 2019-10-20 19:17:18.659799 contacts_show
1 8 2019-10-29 21:26:40.258472 2019-10-29 21:26:40.258472 contacts_show
2 9 2019-10-30 08:01:05.420773 2019-10-30 08:01:05.420773 contacts_show
3 10 2019-11-03 17:12:09.708771 2019-11-03 17:12:09.708771 contacts_show
4 18 2019-10-25 16:44:41.263364 2019-10-25 16:44:41.263364 contacts_show
In [111]:
# считаем количество дней 
advert_open_contacts_show_min['delta_time'] = advert_open_contacts_show_min['time_first'] - advert_open_contacts_show_min['time_min']
advert_open_contacts_show_min.sort_values(by='delta_time').head()
Out[111]:
session_id time_first time_min event_first delta_time
0 6 2019-10-20 19:17:18.659799 2019-10-20 19:17:18.659799 contacts_show 0 days
1130 6957 2019-10-18 14:23:02.984261 2019-10-18 14:23:02.984261 contacts_show 0 days
1129 6952 2019-10-14 14:58:59.584664 2019-10-14 14:58:59.584664 contacts_show 0 days
1128 6951 2019-10-26 19:22:23.859482 2019-10-26 19:22:23.859482 contacts_show 0 days
1127 6945 2019-10-11 16:53:15.400943 2019-10-11 16:53:15.400943 contacts_show 0 days
In [112]:
advert_open_contacts_show_min['session_id'].nunique()
Out[112]:
1703
In [113]:
# считаем количество пустых значений(contacts_show)
advert_open_contacts_show_min.query('delta_time == "0 days 00:00:00"').count()
Out[113]:
session_id     1606
time_first     1606
time_min       1606
event_first    1606
delta_time     1606
dtype: int64
In [114]:
# удалаем пустые значения
advert_open_contacts_show_min = advert_open_contacts_show_min.query('delta_time != "0 days 00:00:00"')
advert_open_contacts_show_min.head()
Out[114]:
session_id time_first time_min event_first delta_time
30 151 2019-10-13 17:37:33.310985 2019-10-13 17:35:48.228892 advert_open 0 days 00:01:45.082093
31 154 2019-10-18 20:05:30.849432 2019-10-18 19:56:21.272541 advert_open 0 days 00:09:09.576891
38 183 2019-10-22 16:36:48.062582 2019-10-22 16:33:06.841621 advert_open 0 days 00:03:41.220961
49 280 2019-10-08 14:14:01.926825 2019-10-08 13:57:12.506935 advert_open 0 days 00:16:49.419890
66 344 2019-10-17 14:29:55.599850 2019-10-17 14:29:02.497882 advert_open 0 days 00:00:53.101968
In [115]:
# время проведенное между действиями
advert_open_contacts_show_min.describe()
Out[115]:
session_id delta_time
count 97.000000 97
mean 4929.701031 0 days 00:08:01.630865659
std 2878.669442 0 days 00:12:21.550018710
min 151.000000 0 days 00:00:02.744644
25% 2246.000000 0 days 00:01:00.977199
50% 5282.000000 0 days 00:02:57.775595
75% 7103.000000 0 days 00:09:51.529992
max 10252.000000 0 days 01:10:51.030238
In [116]:
# переведем дни в минуты
advert_open_contacts_show_min['minutes'] = advert_open_contacts_show_min['delta_time']/np.timedelta64(1,'m')
advert_open_contacts_show_min.head()
Out[116]:
session_id time_first time_min event_first delta_time minutes
30 151 2019-10-13 17:37:33.310985 2019-10-13 17:35:48.228892 advert_open 0 days 00:01:45.082093 1.751368
31 154 2019-10-18 20:05:30.849432 2019-10-18 19:56:21.272541 advert_open 0 days 00:09:09.576891 9.159615
38 183 2019-10-22 16:36:48.062582 2019-10-22 16:33:06.841621 advert_open 0 days 00:03:41.220961 3.687016
49 280 2019-10-08 14:14:01.926825 2019-10-08 13:57:12.506935 advert_open 0 days 00:16:49.419890 16.823665
66 344 2019-10-17 14:29:55.599850 2019-10-17 14:29:02.497882 advert_open 0 days 00:00:53.101968 0.885033
In [117]:
# общее время от advert_open до contacts_show
advert_open_contacts_show_min['minutes'].sum()
Out[117]:
778.63656615
In [118]:
# всего advert_open
advert_open_contacts_show_min['session_id'].nunique()
Out[118]:
97
In [119]:
# среднее время от advert_open до contacts_show
advert_open_contacts_show_min['minutes'].sum()/ advert_open_contacts_show_min['session_id'].nunique()
Out[119]:
8.027181094329897

Среднее время от advert_open до contacts_show составляет 8 минут. Медианное время - 3 минуты.

Считаем конверсию по событиям ADVERT_OPEN -> CONTACTS_SHOW¶
In [120]:
# создаем таблицу с первыми данными по advert_open
advert_open1 = unnecessary_things.sort_values(by=['user_id', 'time'])
advert_open1 = unnecessary_things.query('event == "advert_open"').groupby('user_id')['time'].min().reset_index()
advert_open1.columns = ['user_id', 'time_first']
advert_open1.head()
Out[120]:
user_id time_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-24 10:52:18.644065
1 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-27 00:08:28.225172
2 00b59e77-3dc8-4193-a217-c50b9fe849bf 2019-10-29 15:28:18.987414
3 0164902d-7393-47e1-9d5b-0ec4c0171cdc 2019-10-28 15:16:57.266458
4 017c6afc-965d-4c94-84ee-f0e326998e30 2019-10-10 14:29:11.294613
In [121]:
# создаем данные по первому действию unnecessary_things и contacts_show 
session_id_event_first_time_min17 = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец 
session_id_event_first_time_min17 =session_id_event_first_time_min17.groupby('user_id')\
                                                                    .agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min17.columns = session_id_event_first_time_min17.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min17.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
session_id_event_first_time_min17.reset_index().head()
Out[121]:
user_id time_min event_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 contacts_show
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 contacts_show
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841 contacts_show
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501 contacts_show
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 contacts_show
In [122]:
# соединяем таблицы 
advert_open_contacts_show_min1 = advert_open1.merge(session_id_event_first_time_min17, on='user_id' , how='left')
advert_open_contacts_show_min1.head()
Out[122]:
user_id time_first time_min event_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-24 10:52:18.644065 2019-10-20 19:17:18.659799 contacts_show
1 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-27 00:08:28.225172 NaT NaN
2 00b59e77-3dc8-4193-a217-c50b9fe849bf 2019-10-29 15:28:18.987414 NaT NaN
3 0164902d-7393-47e1-9d5b-0ec4c0171cdc 2019-10-28 15:16:57.266458 NaT NaN
4 017c6afc-965d-4c94-84ee-f0e326998e30 2019-10-10 14:29:11.294613 NaT NaN
In [123]:
advert_open_contacts_show_min1['user_id'].nunique()
Out[123]:
751
In [124]:
advert_open_contacts_show_min1['event_first'].value_counts()
Out[124]:
contacts_show    138
Name: event_first, dtype: int64
In [125]:
# рассчитываем конверсию  В Цeлевое действие ADVERT_OPEN -> CONTACTS_SHOW
con_adv1 = advert_open_contacts_show_min1\
          .groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
con_adv1['advert_open'] = advert_open_contacts_show_min1['user_id'].nunique()
con_adv1['conversion'] = round(con_adv1.user_id /  con_adv1.advert_open * 100, 2)
con_adv1.reset_index()
Out[125]:
event_first user_id advert_open conversion
0 contacts_show 138 751 18.38

Конверсия В Цeлевое действие ADVERT_OPEN(751) -> CONTACTS_SHOW(138) составляет 18.38%

События TIPS_CLICK-> CONTACTS_SHOW¶

Считаем время проведенное между событиями TIPS_CLICK-> CONTACTS_SHOW¶
In [126]:
# создаем таблицу с первыми данными по contacts_show и tips_click
contacts_show1 = table.sort_values(by=['session_id', 'time'])
contacts_show1 = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
contacts_show1.columns = ['session_id', 'time_first']
contacts_show1.head()
Out[126]:
session_id time_first
0 6 2019-10-20 19:17:18.659799
1 8 2019-10-29 21:26:40.258472
2 9 2019-10-30 08:01:05.420773
3 10 2019-11-03 17:12:09.708771
4 18 2019-10-25 16:44:41.263364
In [127]:
# создаем данные по первому действию tips_click и contacts_show 
session_id_tips_click_first_time_min = table.query('source == "tips_click" or source == "contacts_show"').sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец 
session_id_tips_click_first_time_min =session_id_tips_click_first_time_min.groupby('session_id')\
                                                                          .agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_tips_click_first_time_min.columns = session_id_event_first_time_min1.columns.map(''.join)
# меняем название столбцов
session_id_tips_click_first_time_min.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_tips_click_first_time_min.reset_index().head()
Out[127]:
session_id time_min event_first
0 6 2019-10-20 19:17:18.659799 contacts_show
1 8 2019-10-29 21:26:40.258472 contacts_show
2 9 2019-10-30 08:01:05.420773 contacts_show
3 10 2019-11-03 17:12:09.708771 contacts_show
4 18 2019-10-25 16:44:41.263364 contacts_show
In [128]:
# соединяем таблицы 
tips_click_contacts_show_min = contacts_show1.merge(session_id_tips_click_first_time_min, on='session_id', how='left')
tips_click_contacts_show_min.head()
Out[128]:
session_id time_first time_min event_first
0 6 2019-10-20 19:17:18.659799 2019-10-20 19:17:18.659799 contacts_show
1 8 2019-10-29 21:26:40.258472 2019-10-29 21:26:40.258472 contacts_show
2 9 2019-10-30 08:01:05.420773 2019-10-30 08:01:05.420773 contacts_show
3 10 2019-11-03 17:12:09.708771 2019-11-03 17:12:09.708771 contacts_show
4 18 2019-10-25 16:44:41.263364 2019-10-25 16:44:41.263364 contacts_show
In [129]:
# считаем количество дней 
tips_click_contacts_show_min['delta_time'] = tips_click_contacts_show_min['time_first'] - tips_click_contacts_show_min['time_min']
tips_click_contacts_show_min.sort_values(by='delta_time').head()
Out[129]:
session_id time_first time_min event_first delta_time
0 6 2019-10-20 19:17:18.659799 2019-10-20 19:17:18.659799 contacts_show 0 days
1135 6988 2019-11-03 11:43:34.001832 2019-11-03 11:43:34.001832 contacts_show 0 days
1134 6981 2019-10-20 20:28:43.550386 2019-10-20 20:28:43.550386 contacts_show 0 days
1133 6979 2019-10-31 12:26:23.730292 2019-10-31 12:26:23.730292 contacts_show 0 days
1132 6978 2019-10-14 10:44:46.915801 2019-10-14 10:44:46.915801 contacts_show 0 days
In [130]:
# считаем количество пустых значений(contacts_show)
tips_click_contacts_show_min.query('delta_time == "0 days 00:00:00"').count()
Out[130]:
session_id     1675
time_first     1675
time_min       1675
event_first    1675
delta_time     1675
dtype: int64
In [131]:
# удалаем пустые значения
tips_click_contacts_show_min = tips_click_contacts_show_min.query('delta_time != "0 days 00:00:00"')
tips_click_contacts_show_min.head()
Out[131]:
session_id time_first time_min event_first delta_time
97 538 2019-10-13 23:02:17.711053 2019-10-13 22:33:18.694326 tips_click 0 days 00:28:59.016727
179 1152 2019-10-31 14:01:32.965861 2019-10-31 13:52:04.994469 tips_click 0 days 00:09:27.971392
232 1498 2019-10-23 14:36:01.009189 2019-10-23 14:13:38.969895 tips_click 0 days 00:22:22.039294
239 1557 2019-10-07 11:11:50.979295 2019-10-07 11:07:14.538207 tips_click 0 days 00:04:36.441088
243 1565 2019-10-14 07:24:01.706966 2019-10-14 07:19:52.620198 tips_click 0 days 00:04:09.086768
In [132]:
# время проведенное между действиями
tips_click_contacts_show_min.describe()
Out[132]:
session_id delta_time
count 28.000000 28
mean 5155.357143 0 days 00:13:35.650209500
std 2976.691156 0 days 00:13:28.621342675
min 538.000000 0 days 00:00:17.448593
25% 1904.250000 0 days 00:03:55.232924500
50% 4889.000000 0 days 00:07:50.879688
75% 7499.250000 0 days 00:19:54.083226250
max 10022.000000 0 days 00:50:47.995941
In [133]:
# переведем дни в минуты
tips_click_contacts_show_min['minutes'] = tips_click_contacts_show_min['delta_time']/np.timedelta64(1,'m')
tips_click_contacts_show_min.head()
Out[133]:
session_id time_first time_min event_first delta_time minutes
97 538 2019-10-13 23:02:17.711053 2019-10-13 22:33:18.694326 tips_click 0 days 00:28:59.016727 28.983612
179 1152 2019-10-31 14:01:32.965861 2019-10-31 13:52:04.994469 tips_click 0 days 00:09:27.971392 9.466190
232 1498 2019-10-23 14:36:01.009189 2019-10-23 14:13:38.969895 tips_click 0 days 00:22:22.039294 22.367322
239 1557 2019-10-07 11:11:50.979295 2019-10-07 11:07:14.538207 tips_click 0 days 00:04:36.441088 4.607351
243 1565 2019-10-14 07:24:01.706966 2019-10-14 07:19:52.620198 tips_click 0 days 00:04:09.086768 4.151446
In [134]:
# общее время от tips_click до contacts_show
tips_click_contacts_show_min['minutes'].sum()
Out[134]:
380.6367644333334
In [135]:
# всего tips_click
tips_click_contacts_show_min['session_id'].nunique()
Out[135]:
28
In [136]:
# среднее время от tips_click до contacts_show
tips_click_contacts_show_min['minutes'].sum()/ tips_click_contacts_show_min['session_id'].nunique()
Out[136]:
13.594170158333336

Среднее время от tips_click до contacts_show составляет 13 минут. Медианное время - 8 минут.

Считаем конверсию по событиям TIPS_CLICK-> CONTACTS_SHOW¶
In [137]:
# создаем таблицу с первыми данными по tips_click
tips_click_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_click_user = unnecessary_things.query('event == "tips_click"').groupby('user_id')['time'].min().reset_index()
tips_click_user.columns = ['user_id', 'time_first']
tips_click_user.head()
Out[137]:
user_id time_first
0 01147bf8-cd48-49c0-a5af-3f6eb45f8262 2019-11-01 22:20:20.323377
1 01b4ca51-930d-4518-aa09-8a8c35e1d9cc 2019-10-22 11:22:12.442909
2 02ac1c07-7a45-4d4b-9dbe-bea066039c01 2019-10-14 10:47:22.491517
3 02e7c193-842b-4995-b67a-8c87ac0f29bb 2019-10-08 21:03:01.344918
4 034a556c-8837-4c78-8012-795e03764657 2019-11-03 17:05:31.274371
In [138]:
# создаем данные по первому действию unnecessary_things и contacts_show 
session_id_event_first_time_min17 = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец 
session_id_event_first_time_min17 =session_id_event_first_time_min17.groupby('user_id')\
                                                                    .agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min17.columns = session_id_event_first_time_min17.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min17.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
session_id_event_first_time_min17.reset_index().head()
Out[138]:
user_id time_min event_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 contacts_show
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 contacts_show
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841 contacts_show
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501 contacts_show
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 contacts_show
In [139]:
# соединяем таблицы 
tips_click_contacts_show_min1 = tips_click_user.merge(session_id_event_first_time_min17, on='user_id' , how='left')
tips_click_contacts_show_min1.head()
Out[139]:
user_id time_first time_min event_first
0 01147bf8-cd48-49c0-a5af-3f6eb45f8262 2019-11-01 22:20:20.323377 NaT NaN
1 01b4ca51-930d-4518-aa09-8a8c35e1d9cc 2019-10-22 11:22:12.442909 NaT NaN
2 02ac1c07-7a45-4d4b-9dbe-bea066039c01 2019-10-14 10:47:22.491517 NaT NaN
3 02e7c193-842b-4995-b67a-8c87ac0f29bb 2019-10-08 21:03:01.344918 NaT NaN
4 034a556c-8837-4c78-8012-795e03764657 2019-11-03 17:05:31.274371 NaT NaN
In [140]:
tips_click_contacts_show_min1['user_id'].nunique()
Out[140]:
322
In [141]:
# рассчитываем конверсию между уникальными пользователя contacts_show и advert_open
con_tips1 = tips_click_contacts_show_min1\
          .groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
con_tips1['tips_click'] = tips_click_contacts_show_min1['user_id'].nunique()
con_tips1['conversion'] = round(con_tips1.user_id /  con_tips1.tips_click * 100, 2)
con_tips1.reset_index()
Out[141]:
event_first user_id tips_click conversion
0 contacts_show 100 322 31.06

Конверсия в Целевое действие TIPS_CLICK(322)-> CONTACTS_SHOW(100) составляет - 31.06%.

Проверка статистических гипотез¶

Различается ли конверсия в просмотры контактов tips_show+tips_click и tips_show¶

Формулируем гипотезу:¶

Гипотеза H0: между конверсиями действий tips_show + tips_click и tips_show нет значимой разницы

Гипотеза H1: между конверсиями есть значимая разница

Действия tips_show + tips_click¶

In [142]:
# создаем таблицу с первыми данными по tips_click
tips_click_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_click_user = unnecessary_things.query('event == "tips_click"').groupby('user_id')['time'].min().reset_index()
tips_click_user.columns = ['user_id', 'time_first']
tips_click_user.head()
Out[142]:
user_id time_first
0 01147bf8-cd48-49c0-a5af-3f6eb45f8262 2019-11-01 22:20:20.323377
1 01b4ca51-930d-4518-aa09-8a8c35e1d9cc 2019-10-22 11:22:12.442909
2 02ac1c07-7a45-4d4b-9dbe-bea066039c01 2019-10-14 10:47:22.491517
3 02e7c193-842b-4995-b67a-8c87ac0f29bb 2019-10-08 21:03:01.344918
4 034a556c-8837-4c78-8012-795e03764657 2019-11-03 17:05:31.274371
In [143]:
tips_click_user['user_id'].nunique()
Out[143]:
322
In [144]:
# создаем таблицу с первыми данными по tips_show
tips_show_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_show_user = unnecessary_things.query('event == "tips_show"').groupby('user_id')['time'].min().reset_index()
tips_show_user.columns = ['user_id', 'time_first']
tips_show_user.head()
Out[144]:
user_id time_first
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13:39:45.989359
1 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-20 17:48:42.169252
2 00554293-7e00-4122-b898-4e892c4a7c53 2019-10-27 12:33:13.354542
3 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:29:10.270892
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:04:20.485500
In [145]:
tips_show_user['user_id'].nunique()
Out[145]:
2801
In [146]:
# соединяем таблицы 
tips_click_tips_show_user = tips_click_user.merge(tips_show_user, on='user_id')
tips_click_tips_show_user.head()
Out[146]:
user_id time_first_x time_first_y
0 01147bf8-cd48-49c0-a5af-3f6eb45f8262 2019-11-01 22:20:20.323377 2019-11-01 21:13:53.377582
1 01b4ca51-930d-4518-aa09-8a8c35e1d9cc 2019-10-22 11:22:12.442909 2019-10-22 11:06:21.296203
2 02e7c193-842b-4995-b67a-8c87ac0f29bb 2019-10-08 21:03:01.344918 2019-10-18 23:56:01.687840
3 04ee0c31-3c77-49f8-81d2-bbfe9fe2cde8 2019-10-23 16:09:25.426033 2019-10-23 16:08:52.128563
4 0656a1d1-9032-43ae-b936-11e41526eeff 2019-10-27 18:55:52.567024 2019-10-27 18:48:21.967904
In [147]:
# уникальные пользователи tips_click+tips_show
tips_click_tips_show_user['user_id'].nunique()
Out[147]:
297
In [148]:
# создаем данные по первому действию tips_click+tips_show и contacts_show 
contacts_show_user = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец 
contacts_show_user =contacts_show_user.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
contacts_show_user.columns = contacts_show_user.columns.map(''.join)
# меняем название столбцов
contacts_show_user.rename(columns={'timemin': 'time_min', 'eventfirst': 'event'}, inplace = True)
contacts_show_user.reset_index().head()
Out[148]:
user_id time_min event
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 contacts_show
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 contacts_show
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841 contacts_show
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501 contacts_show
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 contacts_show
In [149]:
# соединяем таблицы 
tips_click_tips_show_user1 = tips_click_tips_show_user.merge(contacts_show_user, on='user_id')
tips_click_tips_show_user1.rename(columns={'eventfirst': 'event'}, inplace = True)
tips_click_tips_show_user1.head()
Out[149]:
user_id time_first_x time_first_y time_min event
0 06bdb96e-2712-47b3-a0af-d19f297abd6c 2019-10-12 18:16:02.627749 2019-10-12 17:36:57.950995 2019-10-12 17:52:29.183855 contacts_show
1 081bb564-703e-4f1c-9016-ac9460dec5bf 2019-10-28 18:35:40.638076 2019-10-28 17:10:47.969156 2019-10-29 18:02:08.808218 contacts_show
2 0d03c0d5-8463-40a5-a9bc-c688f7058ddb 2019-10-13 22:33:18.694326 2019-10-13 22:28:19.121602 2019-10-13 23:02:17.711053 contacts_show
3 136b7b37-2bd4-4718-b14a-e38bc3d6d112 2019-10-17 23:49:11.455900 2019-10-07 21:33:19.333950 2019-10-07 21:38:02.944592 contacts_show
4 145009b0-bb47-4239-8afb-ad9a4b21ee2f 2019-10-07 20:37:57.598909 2019-10-07 20:27:26.688851 2019-10-07 20:26:29.099318 contacts_show
In [150]:
# уникальные пользователи contacts_show в tips_click+tips_show
tips_click_tips_show_user1['user_id'].nunique()
Out[150]:
91
In [151]:
# уникальные пользователи tips_click+tips_show
tips_click_tips_show_user['user_id'].nunique()
Out[151]:
297
In [152]:
# собираем уникальных пользователей contacts_show в tips_click+tips_show
conversion_click_show = tips_click_tips_show_user1.pivot_table(index = 'event', values = 'user_id',  aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
# создаем столбец по уникальных пользователей tips_click+tips_show
conversion_click_show['click_show'] = tips_click_tips_show_user['user_id'].nunique()
# находим конверсию
conversion_click_show['conversion'] = round(conversion_click_show['user_id'] / conversion_click_show['click_show']*100,2)
conversion_click_show.reset_index()
Out[152]:
event user_id click_show conversion
0 contacts_show 91 297 30.64

Конверсия в Целевое действие tips_show + tips_click(297)-> contacts_show(91) составляет - 30.64%.

Действия tips_show¶

In [153]:
#создаем таблицу с первыми данными по tips_show
tips_show_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_show_user = unnecessary_things.query('event == "tips_show"').groupby('user_id')['time'].min().reset_index()
tips_show_user.columns = ['user_id', 'time_first']
tips_show_user.head()
Out[153]:
user_id time_first
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13:39:45.989359
1 004690c3-5a84-4bb7-a8af-e0c8f8fca64e 2019-10-20 17:48:42.169252
2 00554293-7e00-4122-b898-4e892c4a7c53 2019-10-27 12:33:13.354542
3 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:29:10.270892
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:04:20.485500
In [154]:
# уникальных пользователей tips_show
tips_show_user['user_id'].nunique()
Out[154]:
2801
In [155]:
# создаем данные по первому действию contacts_show 
contacts_show_user = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец 
contacts_show_user =contacts_show_user.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
contacts_show_user.columns = contacts_show_user.columns.map(''.join)
# меняем название столбцов
contacts_show_user.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
contacts_show_user.reset_index().head()
Out[155]:
user_id time_min event_first
0 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:17:18.659799 contacts_show
1 00551e79-152e-4441-9cf7-565d7eb04090 2019-10-25 16:44:41.263364 contacts_show
2 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:22:54.442841 contacts_show
3 00753c79-ea81-4456-acd0-a47a23ca2fb9 2019-10-20 14:57:06.080501 contacts_show
4 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:08:09.140381 contacts_show
In [156]:
# соединяем таблицы 
contact_show_user = tips_show_user.merge(contacts_show_user, on='user_id')
contact_show_user.head()
Out[156]:
user_id time_first time_min event_first
0 005fbea5-2678-406f-88a6-fbe9787e2268 2019-10-11 11:29:10.270892 2019-10-11 11:22:54.442841 contacts_show
1 007d031d-5018-4e02-b7ee-72a30609173f 2019-10-22 13:04:20.485500 2019-10-22 13:08:09.140381 contacts_show
2 0103a07d-513f-42b9-8d91-d5891d5655fe 2019-11-03 18:42:59.275533 2019-11-03 18:50:04.133976 contacts_show
3 035ae717-a6ae-4569-b952-16be9447832b 2019-10-27 16:52:54.870118 2019-10-27 16:58:12.318501 contacts_show
4 03739d15-7212-415c-9c88-4dfc24b8d3b5 2019-10-24 10:33:29.694673 2019-10-24 10:19:36.719134 contacts_show
In [157]:
# уникальных пользователей в tips_show - contacts_show 
contact_show_user['user_id'].nunique()
Out[157]:
516
In [158]:
# преобразования массива в список
contact_show_user = contact_show_user['user_id'].unique().tolist()
tips_click_tips_show_user1 = tips_click_tips_show_user1['user_id'].unique().tolist()
# уникальные пользователи tips_show (516-91) в contacts_show
not_click = list(set(contact_show_user) - set(tips_click_tips_show_user1))
# подставляем в таблицу
not_click_contact_user = unnecessary_things.query('user_id == @not_click')[['user_id', 'event']]
not_click_contact_user['user_id'].nunique()
Out[158]:
425
In [159]:
# преобразования массива в список
tips_show_user1 = tips_show_user['user_id'].unique().tolist()
tips_click_tips_show_user1 = tips_click_tips_show_user['user_id'].unique().tolist()
# уникальные пользователи tips_show(2801-297)
not_tips_click1 = list(set(tips_show_user1) - set(tips_click_tips_show_user1))
# подставляем в таблицу
not_click_user = unnecessary_things.query('user_id == @not_tips_click1')[['user_id', 'event']]
not_click_user['user_id'].nunique()
Out[159]:
2504
In [160]:
# собираем уникальных пользователей contacts_show в tips_click+tips_show
conversion_show = not_click_contact_user.query('event == "tips_show"').pivot_table(index = 'event', values = 'user_id',  aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
# создаем столбец по уникальных пользователей tips_click+tips_show
conversion_show['count'] = not_click_user['user_id'].nunique()
# находим конверсию
conversion_show['conversion'] = round(conversion_show['user_id'] / conversion_show['count']*100,2)
# переименовываем столбец
conversion_show.rename(columns={'user_id':'contacts_show'}, inplace = True)
conversion_show.reset_index()
Out[160]:
event contacts_show count conversion
0 tips_show 425 2504 16.97

Конверсия в Целевое действие tips_show(2504) -> contacts_show(425) составляет - 16.97%.

In [161]:
tips_click_tips_show_user1 = unnecessary_things.query('user_id == @tips_click_tips_show_user1')[['user_id', 'event']]
In [162]:
def z_test(not_click_user, tips_click_tips_show_user):
    
    # критический уровень статистической значимости
    alpha = 0.05
 
    # число пользователей в группе 1 и группе 2:
    users_new = np.array([not_click_user['user_id'].nunique(), 
                         tips_click_tips_show_user['user_id'].nunique()])

    # число пользователей, совершивших событие в группе 1 и группе 2
    success = np.array([not_click_user[not_click_user['event'] == 'contacts_show']['user_id'].nunique(), 
                        tips_click_tips_show_user1[tips_click_tips_show_user1['event'] == 'contacts_show']['user_id'].nunique()])    
    # пропорции успехов в группах:
    p1 = success[0]/users_new[0]
    p2 = success[1]/users_new[1]
    
    # пропорция успехов в комбинированном датасете:
    p_combined = (success[0] + success[1]) / (users_new[0] + users_new[1])

    # разница пропорций в датасетах
    difference = p1 - p2 

    # считаем статистику в ст.отклонениях стандартного нормального распределения
    z_value = difference /  np.sqrt(p_combined * (1 - p_combined) * (1/users_new[0] + 1/users_new[1]))

    # задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
    distr = st.norm(0, 1)  

    p_value = (1 - distr.cdf(abs(z_value))) * 2   #тест двусторонний, удваиваем результат
    
    
    print('p-значение: ', p_value)

    if p_value < alpha:
        print('Отвергаем нулевую гипотезу: между конверсиями действий tips_show + tips_click и tips_show есть разница')
    else:
        print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию действий tips_show + tips_click и tips_show разными.')
In [163]:
# считаем статистическую значимость между tips_click + tips_show и tips_show
z_test(not_click_user, tips_click_tips_show_user)
print()
p-значение:  9.218316554537864e-09
Отвергаем нулевую гипотезу: между конверсиями действий tips_show + tips_click и tips_show есть разница

Различается ли конверсия просмотры контактов по источникам yandex и google¶

Формулируем гипотезу:¶

Гипотеза H0: между конверсиями источников yandex и google нет значимой разницы

Гипотеза H1: между конверсиями есть значимая разница

In [164]:
# Сделам таблицу с числом уникальных пользователей по событиям и источникам yandex и google
yandex_google1 = unnecessary_things.query('source == "yandex" and event == "contacts_show" or source == "google" and event == "contacts_show"')
count_yandex_google = yandex_google1.pivot_table(index='source',columns = 'event', values='user_id',aggfunc='nunique').sort_values(by = 'source', ascending = True)
count_yandex_google['count'] = unnecessary_things.pivot_table(index = 'source', values = 'user_id',  aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
count_yandex_google['conversion'] = round(count_yandex_google['contacts_show'] / count_yandex_google['count']*100,2)
count_yandex_google.sort_values(by = 'source', ascending = False).reset_index()
Out[164]:
event source contacts_show count conversion
0 yandex 478 1934 24.72
1 google 275 1129 24.36
In [165]:
google1  = unnecessary_things.query('source == "google"')
yandex1  = unnecessary_things.query('source == "yandex"')
In [166]:
google1['user_id'].nunique()
Out[166]:
1129
In [167]:
yandex1['user_id'].nunique()
Out[167]:
1934
In [168]:
google1[google1['event'] == 'contacts_show']['user_id'].nunique()
Out[168]:
275
In [169]:
yandex1[yandex1['event'] == 'contacts_show']['user_id'].nunique()
Out[169]:
478
In [170]:
def z_test(google1, yandex1):
    
    # критический уровень статистической значимости
    alpha = 0.05
 
    # число пользователей в группе 1 и группе 2:
    users_new = np.array([google1['user_id'].nunique(), 
                          yandex1['user_id'].nunique()])

    # число пользователей, совершивших событие в группе 1 и группе 2
    success = np.array([google1[google1['event'] == 'contacts_show']['user_id'].nunique() , 
                        yandex1[yandex1['event'] == 'contacts_show']['user_id'].nunique()])    
    # пропорции успехов в группах:
    p1 = success[0]/users_new[0]
    p2 = success[1]/users_new[1]
    
    # пропорция успехов в комбинированном датасете:
    p_combined = (success[0] + success[1]) / (users_new[0] + users_new[1])

    # разница пропорций в датасетах
    difference = p1 - p2 

    # считаем статистику в ст.отклонениях стандартного нормального распределения
    z_value = difference /  np.sqrt(p_combined * (1 - p_combined) * (1/users_new[0] + 1/users_new[1]))

    # задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
    distr = st.norm(0, 1)  

    p_value = (1 - distr.cdf(abs(z_value))) * 2   #тест двусторонний, удваиваем результат
    
    
    print('p-значение: ', p_value)

    if p_value < alpha:
        print('Отвергаем нулевую гипотезу: между конверсиями есть разница')
    else:
        print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию между источниками yandex и google разными.')
In [171]:
# считаем статистическую значимость между источниками yandex и google
z_test(google1, yandex1)
print()
p-значение:  0.8244316027993777
Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию между источниками yandex и google разными.

Шаг Выводы и рекомендации¶

ВЫВОДЫ¶

Данные предоставлены за 28 дней с 07 октября по 03 ноября 2019 г. Всего в данных 74197 событий и 4293 пользователя. В среднем на пользователя приходится 17 событий. Пользователи предпочитают пользоваться приложение в буднии дни. В данных 3 источника:yandex(34286), google(20445), other(19466). В представленных данных 9 действий: tips_show(40055), photos_show(10012), search(6784), advert_open(6164), contacts_show(4529), map(3881), favorites_add(1417), tips_click(814), contacts_call(541). Retention Rate коэффициент удержания низкий 24% и соответсвенно Churn Rate коэффициент отскока высокий 76%. Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073. Среднее время от первого действия до Целового составляет 10 минут.Чтобы дойти до Целевого действия больше всего времени уходит на tips_click(клик по рекомендованному объявлению) 26 минут. На map(открыл карту объявления) , tips_show(увидел рекомендованное объявление) и photos_show(посмотрел фотографии объявлений) тратится 10 минут. Меньше всего времени уходит advert_open(открыл карточки объявления)- 5 минут. До просмотра контактов пользователь совершает следующие действия: tips_show(увидел рекомендованное объявление)- 260, search(выполнил поиск по сайту) - 209, photos_show(посмотрел фотографии) - 162, map(отpкрыл карту объявлений) - 140, favorites_add (добавил объявление в избранное) - 17, advert_open(открыл карточку объявлений) - 12 и затем tips_click(кликнул по рекомендованному объвлению) - 2.

Пользователи доходят до Целевого действия через поиск по приложению:search(100%)-tips_show(48%)-contacts_show(47%). Через открытия карточки объявления до целевого действия: advert_open(100%) - tips_show(79%)-contacts_show(23%). Пользователи доходят до Целевого действия через открытие карты объявления: map(100%) - tips_show(93%)-contacts_show(21%).

Выше всего конверсия в Целевое действие TIPS_CLICK(322)-> CONTACTS_SHOW(100) составляет - 31.06%.

На втором месте конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.

На третьем месте конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.

На четвертом месте конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.

Между конверсиями по действиям: tips_show + tips_click и tips_show есть значимая разница: Конверсия в Целевое действие tips_show + tips_click(297)-> contacts_show(91) составляет - 30.64%. Конверсия в Целевое действие tips_show(2504) -> contacts_show(425) составляет - 16.97%.

Между конверсиями по источникам: yandex и google нет значимой разницы: Конверсия просмотры контактов по источникам yandex составляет 24.72%. Конверсия просмотры контактов по источникам google составляет 24.36%

Рекомендации¶

Усовершенствовать алгоритм подбора рекомендованных объявлений. При настройке кампаний нужно составить объявления так, чтобы на них кликали. Для этого нужно соблюсти основные правила:

  1. Тщательно собирать и прорабатывать семантику.
  2. Заполнять все доступные поля в объявлении.
  3. Вписывать ключевые слова в заголовки и текст объявления.
  4. Добавлять в текст УТП, которые выделят нас среди конкурентов.

Рассмотреть возможность услуги по доставке товаров до покупателя. Для увеличения пользовательской активности сделать систему скидок по количеству покупаемых товаров. За оставленные положительные отзывы премировать в виде скидок и бесплатной доставки. Ежемесячно проводить рассылку пользователям по раннее интерисующим его товарам.